c# system.reactive

c# - Cómo exponer las propiedades IObservable<T> sin utilizar el campo de respaldo del Tema<T>



system.reactive (4)

En una respuesta en el foro de Rx , Dave Sexton (de Rxx ), dijo como parte de una respuesta a algo:

Los sujetos son los componentes de estado de Rx. Son útiles para cuando necesita crear un observable similar a un evento como un campo o una variable local.

¿Qué es exactamente lo que está sucediendo con esta pregunta? También escribió una publicación de blog de seguimiento en profundidad sobre ¿ Usar el tema o No usar el tema? que concluye con:

¿Cuándo debo usar un tema?

Cuando todos los siguientes son verdaderos:

  • no tienes un objeto observable ni nada que pueda convertirse en uno.
  • Usted necesita un observable caliente.
  • El alcance de su observable es un tipo.
  • no es necesario definir un evento similar y ya no existe ningún evento similar.

¿Por qué debería usar un tema en ese caso?

¡Porque no tienes elección!

Por lo tanto, responder a la pregunta interna de "detalles sobre por qué usar Sujeto de esta manera es una mala idea", no es una mala idea, este es uno de los pocos lugares en los que un Sujeto usa la manera correcta de hacer las cosas.

En esta respuesta a una pregunta sobre el Subject<T> Enigmatividad mencionada:

a un lado, debe tratar de evitar el uso de sujetos en absoluto. La regla general es que si estás usando un tema, entonces estás haciendo algo mal.

A menudo utilizo temas como campos de respaldo para IObservable propiedades de IObservable , que probablemente habrían sido eventos .NET en los días anteriores a Rx. por ejemplo, en lugar de algo como

public class Thing { public event EventHandler SomethingHappened; private void DoSomething() { Blah(); SomethingHappened(this, EventArgs.Empty); } }

yo podría hacer

public class Thing { private readonly Subject<Unit> somethingHappened = new Subject<Unit>(); public IObservable<Unit> SomethingHappened { get { return somethingHappened; } } private void DoSomething() { Blah(); somethingHappened.OnNext(Unit.Default); } }

Entonces, si quiero evitar usar el Subject ¿cuál sería la forma correcta de hacer este tipo de cosas? ¿O debería seguir usando eventos .NET en mis interfaces, incluso cuando sean consumidos por el código Rx (así que probablemente FromEventPattern )?

Además, un poco más de detalles sobre por qué usar Subject como este es una mala idea sería útil.

Actualización : para hacer esta pregunta un poco más concreta, estoy hablando de usar el Subject<T> como una forma de pasar del código no Rx (quizás esté trabajando con algún otro código heredado) al mundo de Rx. Entonces, algo como:

class MyVolumeCallback : LegacyApiForSomeHardware { private readonly Subject<int> volumeChanged = new Subject<int>(); public IObservable<int> VolumeChanged { get { return volumeChanged.AsObservable(); } } protected override void UserChangedVolume(int newVolume) { volumeChanged.OnNext(newVolume); } }

Donde, en lugar de usar eventos, el tipo LegacyApiForSomeHardware hace que sobrescribas los métodos virtuales como una forma de obtener notificaciones de "esto acaba de suceder".


Por un lado, alguien puede devolver el SomethingHappen a un objeto IS y agregarle cosas desde afuera. Como mínimo, aplique AsObservable a este para ocultar el asunto del objeto subyacente.

Además, la transmisión por temas de devoluciones de llamada no hace estrictamente más que un evento .NET. Por ejemplo, si un observador lanza, los siguientes en la cadena no serán llamados.

static void D() { Action<int> a = null; a += x => { Console.WriteLine("1> " + x); }; a += x => { Console.WriteLine("2> " + x); if (x == 42) throw new Exception(); }; a += x => { Console.WriteLine("3> " + x); }; a(41); try { a(42); // 2> throwing will prevent 3> from observing 42 } catch { } a(43); } static void S() { Subject<int> s = new Subject<int>(); s.Subscribe(x => { Console.WriteLine("1> " + x); }); s.Subscribe(x => { Console.WriteLine("2> " + x); if (x == 42) throw new Exception(); }); s.Subscribe(x => { Console.WriteLine("3> " + x); }); s.OnNext(41); try { s.OnNext(42); // 2> throwing will prevent 3> from observing 42 } catch { } s.OnNext(43); }

En general, la persona que llama está muerta una vez que el observador lanza, a menos que proteja todas las llamadas On * (pero no trague las excepciones de forma arbitraria, como se muestra arriba). Esto es lo mismo para los delegados de multidifusión; las excepciones se te devolverán.

La mayoría de las veces, puede lograr lo que quiere hacer sin un sujeto, por ejemplo, utilizando Observable.Create para construir una nueva secuencia. Tales secuencias no tienen una "lista de observadores" que resulta de múltiples suscripciones; cada observador tiene su propia "sesión" (el modelo observable en frío), por lo que una excepción de un observador no es más que un comando de suicidio en un área confinada en lugar de volarse en medio de una casilla.

Esencialmente, los sujetos se utilizan mejor en los bordes del gráfico de consulta reactiva (para flujos de ingreso que deben ser direccionables por otra parte que alimenta los datos, aunque podría usar eventos .NET regulares para esto y vincularlos a Rx usando FromEvent * métodos) y para compartir suscripciones dentro de un gráfico de consulta reactiva (utilizando Publicar, Reproducir, etc., que son llamadas de multidifusión disfrazadas, usando un tema). Uno de los peligros de usar sujetos, que son muy constantes debido a su lista de observadores y posible registro de mensajes, es usarlos cuando se intenta escribir un operador de consulta utilizando sujetos. El 99,999% de las veces, tales historias tienen un final triste.


Si bien no puedo hablar directamente por Enigmativity, me imagino que es porque es de muy bajo nivel, algo que realmente no necesitas usar directamente; todo lo que ofrece la clase Subject<T> se puede lograr utilizando las clases en el System.Reactive.Linq nombres System.Reactive.Linq .

Tomando el ejemplo de la documentación del Subject<T> :

Subject<string> mySubject = new Subject<string>(); //*** Create news feed #1 and subscribe mySubject to it ***// NewsHeadlineFeed NewsFeed1 = new NewsHeadlineFeed("Headline News Feed #1"); NewsFeed1.HeadlineFeed.Subscribe(mySubject); //*** Create news feed #2 and subscribe mySubject to it ***// NewsHeadlineFeed NewsFeed2 = new NewsHeadlineFeed("Headline News Feed #2"); NewsFeed2.HeadlineFeed.Subscribe(mySubject);

Esto se logra fácilmente con el método de extensión Merge en la clase Observable :

IObservable<string> feeds = new NewsHeadlineFeed("Headline News Feed #1").HeadlineFeed.Merge( new NewsHeadlineFeed("Headline News Feed #2").HeadlineFeed);

Que luego puede suscribirse a normalmente. Usar Subject<T> solo hace que el código sea más complejo. Si va a utilizar el Subject<T> entonces debería estar haciendo un procesamiento de observables de muy bajo nivel en el que los métodos de extensión le fallan.


Un enfoque para las clases que tienen eventos ToObservable y ToObservable , es proporcionar un método ToObservable que crea un observable frío significativo basado en un evento. Esto es más legible que usar los métodos de fábrica observables, y permite a los desarrolladores que no usan Rx hacer uso de la API.

public IObservable<T> ToObservable() { return Observable.Create<T>(observer => { Action notifier = () => { switch (Status) { case FutureStatus.Completed: observer.OnNext(Value); observer.OnCompleted(); break; case FutureStatus.Cancelled: observer.OnCompleted(); break; case FutureStatus.Faulted: observer.OnError(Exception); break; } }; Resolve += notifier; return () => Resolve -= notifier; }); }