c# .net events system.reactive

c# - En caso de que IObservable sea preferible a los eventos al exponer notificaciones en una biblioteca dirigida a.NET 4+



events system.reactive (4)

El IObservable es el IEnumerable de eventos, por lo que la única pregunta aquí es si crees que IObservable se convertirá en el estándar, ya que IEnumerable es ahora así que sí, es preferible, si crees que es solo una cosa pasajera que caerá en el futuro event uso.

El IObservable es mejor que el event en la mayoría de los casos, pero personalmente creo que me olvidaré de usarlo, ya que no es muy común y, cuando llegue el momento, lo habré olvidado.

Sé que mi respuesta no es de gran ayuda, pero solo el tiempo dirá si RX se convertirá en el estándar, creo que tiene buenas posibilidades.

[EDITAR] Para hacerlo más concreto.

Para el usuario final, la única diferencia es que una es una interfaz y la otra no, lo que hace que la interfaz sea más comprobable y ampliable, ya que diferentes fuentes pueden implementar la misma interfaz.

Como dijo Adam Houldsworth , uno puede cambiarse fácilmente al otro para no hacer otra diferencia.

Tengo una biblioteca .NET que, como parte de un Modelo de Objeto, emitirá notificaciones de ciertas ocurrencias.

Me parece que las principales ventajas de los eventos son la accesibilidad para los principiantes (y la simplicidad en ciertos contextos de consumo), con el principal aspecto negativo de que no se pueden componer y, por lo tanto, se ven obligados a ingresar en un Observable.FromEvent * si desea hacer algo. Interesante sin escribir un matorral de código.

La naturaleza del problema que se está resolviendo es tal que el tráfico de eventos no será particularmente frecuente o voluminoso (definitivamente no es RX de gritos), pero definitivamente no hay un requisito para admitir versiones .NET anteriores a 4.0 [y por lo tanto puedo usar el Interfaz IObservable en System.Reactive sin forzar ninguna dependencia significativa de los consumidores]. Sin embargo, me interesan algunas pautas generales, algunas razones concretas para preferir IObservables en lugar de event desde una perspectiva de diseño de API, independientemente de dónde se encuentre mi caso específico en el event , IObservable spectrum.

Entonces, la pregunta:

¿Hay algo concreto que estoy haciendo dramáticamente más difícil o problemático para los consumidores de API si voy con la cosa más simple y IObservable un event lugar de un IObservable

O bien, replanteado: Aparte de que el consumidor tiene que hacer un Observable.FromEvent * para poder componer eventos, ¿no existe realmente una sola razón para preferir un IObservable sobre un event al exponer una notificación en una API?

Las citas de los proyectos que utilizan IObservable para cosas no codificadoras o las directrices de codificación serían ideales pero no son críticas.

NB, como se mencionó en los comentarios con @Adam Houldsworth, estoy interesado en cosas concretas en la superficie API de una biblioteca .NET 4+, no en una encuesta de opiniones sobre cuál representa una mejor "arquitectura predeterminada" para nuestros tiempos: )

NB: esta pregunta se abordó en IObserver y IObservable en C # para Observer vs Delegates, Events y IObservable vs Plain Events o ¿Por qué debo usar IObservable? . El aspecto de la pregunta que formulo no se ha abordado en ninguna de las respuestas debidas a violaciones de SRP. Otra pregunta ligeramente superpuesta es ¿ Ventajas de .NET Rx sobre eventos clásicos? . (Uso de IObservable en lugar de eventos) [ Uso de IObservable en lugar de eventos cae en esa misma categoría.


En los comentarios de esta respuesta, OP refinó su pregunta como:

[¿Es] de hecho definitivamente el caso de que todos y cada uno de los eventos siempre puedan ser Adaptados para ser IObservable ?

A esa pregunta, la respuesta es básicamente sí, pero con algunas advertencias. También tenga en cuenta que lo contrario no es cierto; consulte la sección sobre transformación inversa y la conclusión por razones por las que los observables podrían preferirse a los eventos clásicos debido al significado adicional que pueden transmitir.

Para una traducción estricta, todo lo que debemos hacer es mapear el evento, que debe incluir al remitente y los argumentos, en una invocación de OnNext . El método de ayuda Observable.FromEventPattern hace un buen trabajo de esto, con las sobrecargas devolviendo IObservable<EventPattern<T>> proporcionando tanto el objeto remitente como EventArgs .

Advertencias

Recordemos la gramática de Rx. Esto se puede indicar en forma EBNF como: Observable Stream = { OnNext }, [ OnError | OnCompleted ] Observable Stream = { OnNext }, [ OnError | OnCompleted ] - o 0 o más eventos OnNext opcionalmente seguidos de OnCompleted o OnError.

Implícita en esto está la idea de que, desde la vista de un suscriptor individual, los eventos no se superponen. Para ser claros, esto significa que no se llamará a un suscriptor al mismo tiempo. Además, es bastante posible que se pueda llamar a otros suscriptores no solo concurrentemente sino también en diferentes momentos. A menudo, son los propios suscriptores los que controlan el ritmo del flujo de eventos (crean contrapresión) manejando eventos más lentos que el ritmo al que llegan. En esta situación, los operadores de Rx típicos hacen cola contra suscriptores individuales en lugar de mantener todo el conjunto de suscriptores. En contraste, los orígenes de eventos .NET clásicos generalmente se transmitirán a los suscriptores en el paso de bloqueo, a la espera de que todos los suscriptores procesen un evento por completo antes de continuar. Este es el comportamiento asumido de larga data para los eventos clásicos, pero en realidad no está decretado en ningún lugar.

La especificación del lenguaje C # 5.0 (este es un documento de Word, consulte la sección 1.6.7.4) y las Directrices de diseño de .NET Framework: el diseño del evento tiene muy poco que decir sobre el comportamiento del evento. La especificación observa que:

La noción de generar un evento es precisamente equivalente a invocar al delegado representado por el evento; por lo tanto, no hay construcciones de lenguaje especiales para generar eventos.

La Guía de programación de C #: sección de eventos dice que:

Cuando un evento tiene varios suscriptores, los controladores de eventos se invocan de forma sincrónica cuando se genera un evento. Para invocar eventos de forma asíncrona, consulte Cómo llamar a métodos sincrónicos de forma asíncrona.

Por lo tanto, los eventos clásicos se emiten tradicionalmente en serie invocando una cadena de delegados en un solo hilo, pero no hay tal restricción en las directrices, y ocasionalmente vemos una invocación paralela de delegados, pero incluso aquí, generalmente, dos instancias de un evento se plantean en serie. Si cada uno se emite en paralelo.

No hay nada en ningún lugar que pueda encontrar en las especificaciones oficiales que establezca explícitamente que las instancias de eventos en sí mismas se deben presentar o recibir en serie. Para decirlo de otra manera, no hay nada que diga que no se pueden generar simultáneamente varias instancias de un evento.

Esto contrasta con los observables donde se establece explícitamente en las Pautas de diseño de Rx que debemos:

4.2. Supongamos que las instancias de observador se llaman de forma serializada

Observe cómo esta declaración solo aborda el punto de vista de una instancia de suscriptor individual y no dice nada acerca de los eventos que se envían simultáneamente a través de las instancias (lo que de hecho es muy común en Rx).

Así que dos takeways:

  • Mientras que OnNext captura la idea de un evento, es posible que el evento clásico .NET pueda violar la gramática de Rx al invocar eventos simultáneamente.
  • Es común que los Rx Observables puros tengan diferentes semánticas en torno a la entrega de eventos bajo carga porque la contrapresión generalmente se maneja por suscriptor en lugar de por evento.

Siempre y cuando no genere eventos simultáneamente en su API, y no le importe la semántica de contrapresión, entonces la traducción a una interfaz observable a través de un mecanismo como el Observable.FromEvent de Observable.FromEvent va a estar bien.

Transformación inversa

En la transformación inversa, tenga en cuenta que OnError y OnCompleted no tienen analogía en los eventos .NET clásicos, por lo que no es posible realizar el mapeo inverso sin una maquinaria adicional y un uso acordado.

Por ejemplo, uno podría traducir OnError y OnCompleted a eventos adicionales, pero esto definitivamente está saliendo del territorio del evento clásico. Además, se requeriría algún mecanismo de sincronización muy incómodo a través de los diferentes manejadores; en Rx, es bastante posible que un suscriptor reciba un OnCompleted mientras que otro aún recibe eventos OnNext ; es mucho más difícil lograr esto en una transformación clásica de eventos .NET.

Los errores

Teniendo en cuenta el comportamiento en casos de error: es importante distinguir un error en el origen del evento de uno en los manejadores / suscriptores. OnError está ahí para lidiar con el primero, en este último caso, tanto los eventos clásicos como Rx simplemente (y correctamente) explotan. Este aspecto de los errores se traduce bien en cualquier dirección.

Conclusión

Los eventos clásicos y observables de .NET no son isomorfos. Puede traducir de eventos a observables de manera razonablemente fácil siempre y cuando se adhiera a los patrones de uso normales. Podría ser el caso de que su API requiera la semántica adicional de observables que no se pueden modelar tan fácilmente con eventos .NET y, por lo tanto, tiene más sentido ir solo observable, pero esta es una consideración específica en lugar de una general, y más de un diseño cuestión que una técnica.

Como orientación general, sugiero una preferencia por los eventos clásicos, si es posible, ya que se comprenden ampliamente y están bien respaldados y se pueden transformar, pero no dude en usar observables si necesita la semántica adicional de error de fuente o terminación representada en forma elegante de eventos OnError y OnCompleted.


He estado leyendo mucho acerca de las extensiones reactivas antes de finalmente bañar mi dedo del pie, y luego de algunos inicios, las encontré muy interesantes y útiles.

La extensión de Observables tiene este encantador parámetro opcional donde puedes pasar tu propio administrador de tiempo, lo que te permite manipular el tiempo . En mi caso, me ayudó mucho ya que estaba haciendo un trabajo relacionado con el tiempo (revise este servicio web cada diez minutos, envíe un correo electrónico por minuto, etc.) e hizo que la prueba fuera una brisa. TestScheduler el TestScheduler en el componente bajo prueba y simularía un día de eventos en un instante.

Entonces, si tiene algunos flujos de trabajo en su biblioteca en los que el tiempo juega un papel importante en la orquestación, realmente recomendaría el uso de Observables como salida.

Sin embargo, si solo está generando eventos en respuestas directas a las entradas de los usuarios, no creo que valga la pena la complejidad agregada para sus usuarios. Como notaste, pueden envolver el evento en su propio Observable si es necesario.

Podrías tener tu pastel y comerlo también, aunque eso significaría más trabajo; ofrezca una fachada que convierta su biblioteca de eventos en una basada en Observable, creando dicho Observable a partir de sus eventos. O hágalo de otra manera: tenga una fachada opcional que se adhiera a su observable y genere un evento clásico cuando se active.

En mi opinión, hay que tomar un paso técnico no trivial cuando se trata de extensiones reactivas y, en este caso, puede deberse a lo que sus consumidores de API serían más cómodos de usar.


Para dirigir su pregunta del título:

No, no deben ser preferidos sobre la base de que existen en .NET 4 y están disponibles para su uso. La preferencia depende del uso previsto, por lo que una preferencia general no está justificada.

Dicho esto, me inclino por ellos como un modelo alternativo a los eventos tradicionales de C #.

Como he comentado a lo largo de esta pregunta, hay muchos beneficios complementarios al acercarse a la API con IObservable , entre los cuales se encuentra el soporte externo y el rango de elección disponible para el consumidor final.

Para abordar su pregunta interna:

Creo que habría poca dificultad entre exponer eventos o IObserable en su API, ya que en ambos casos hay una ruta a la otra. Esto pondría una capa sobre su API, pero en realidad esta es una capa que también podría liberar.

Es mi opinión que elegir uno sobre el otro no va a ser parte de la razón decisiva por la que alguien elige usar o no su API.

Para responder a su pregunta reiterada:

La razón podría encontrarse en la razón por la que hay un Observable.FromEvent en primer lugar :-) IObservable está ganando soporte en muchos lugares en .NET para la programación reactiva y forma parte de muchas bibliotecas populares (Rx, Ix, ReactiveUI), y también Interopera bien con LINQ y IEnumerable y más allá de los gustos de TPL y TPL DataFlow.

Un ejemplo no Rx del patrón observable, por lo que no específicamente IObservable sería ObservableCollection para aplicaciones XAML.