2012-05-07 9 views
9

Leggendo msdn sulle estensioni reattive e così, ho trovato una raccomandazione che dice che non dovrei implementare IObservable, piuttosto uso Observable.Create ... Nel momento in cui ho letto questo, il mio progetto aveva già una classe ObservableImplementation<T>, che avevo usato come una sorgente IObservable, ovunque volessi trasformare gli eventi in Observables.Perché non dovrei implementare IObservable <T>?

Ho letto l'implementazione AbstractObservable<T> in System.Reactive e non ho rilevato alcuna differenza importante tra il loro codice e il mio. Quindi cosa c'è di sbagliato nell'implementazione di IObservable? Posso aggiungere le mie proprietà, e così via ...

per pienezza, ecco la mia implementazione, per favore dimmi se ho fatto qualcosa di sbagliato!

public sealed class ObservableImplementation<T> : IObservable<T> 
{ 
    class Subscription : IDisposable 
    { 
     private readonly Action _onDispose; 
     public Subscription(Action onDispose) 
     { 
      _onDispose = onDispose; 
     } 

     public void Dispose() 
     { 
      _onDispose(); 
     } 
    } 


    public void Raise(T value) 
    { 
     _observers.ForEach(o => o.OnNext(value)); 
    } 
    public void Completion() 
    { 
     _observers.ForEach(o => o.OnCompleted()); 
     _observers.Clear(); 
    } 

    private readonly List<IObserver<T>> _observers = new List<IObserver<T>>(); 
    public IDisposable Subscribe(IObserver<T> observer) 
    { 
     var subscription = new Subscription(() => _observers.Remove(observer)); 
     _observers.Add(observer); 
     return subscription; 
    } 
    public bool AnyObserverPresent { get { return _observers.Any(); } } 
} 
+0

Cordiali saluti, c'è già un'implementazione della classe concreta di 'IObservable ':' Subject' –

+0

... anche un 'IDisposable' basato sui delegati può essere creato usando' Disposable.Create' –

+0

Ho trovato il secondo e ho intenzione di sostituirlo, ma Subject è molto più di un semplice Observable implementazione, dal momento che è anche osservabile. No? – TDaver

risposta

7

Il motivo non è necessario implementare IObservable<T> è la stessa ragione che di solito non implementare IEnumerable<T>, è che qualcuno ha molto probabilmente già costruito la cosa che si desidera. In questo caso, hai praticamente reimplementato lo Subject<T> per la maggior parte.

Edit: Per quanto riguarda la domanda pigro nei commenti, mi piacerebbe implementare che in questo modo:

var lazyObservable = Observable.Create<TFoo>(subj => { /*TODO: Implement Me to load from reflection*/ }) 
    .Multicast(new Subject<TFoo>()) // This means it'll only calc once 
    .RefCount(); // This means it won't get created until someone Subscribes 
+0

Grazie, controllerò la cosa pigra il prima possibile! – TDaver

21

Ci sono alcune ragioni per cui non è consigliabile alle persone di implementare IObservable <T> direttamente.

Uno è la mancanza di protezione contro le violazioni della grammatica dell'osservatore. Ad esempio, la sequenza può mostrare il comportamento delle chiamate OnNext dopo una chiamata OnCompleted, che non è valida. The Observable.Create <T> metodo e ObservableBase <T> tipo di base fare attenzione a ciò, staccando automaticamente l'osservatore alla ricezione di un messaggio terminale. Quindi, anche se il tuo codice fa la cosa sbagliata, l'osservatore non vede una sequenza malformata.

Btw, questo è simile agli iteratori in C#. Implementazione di IEnumerable <T> manualmente deve essere tale che, quando MoveNext di un enumeratore restituisce false (analogo a OnCompleted), le chiamate successive non cambiano la loro mente e iniziare restituendo vero (analogo a OnNext):

Se MoveNext passa la Alla fine della raccolta, l'enumeratore viene posizionato dopo l'ultimo elemento della raccolta e MoveNext restituisce false. Quando l'enumeratore si trova in questa posizione, le successive chiamate a MoveNext restituiscono anche false finché non viene chiamato Reset. (Fonte: MSDN)

Quando si utilizzano gli iteratori in C# 2.0 o VB 11.0, tali problemi sono presi in considerazione. Questo è simile al nostro metodo Observable.Create <T> e ObservableBase <T>.

Un motivo correlato alla discussione precedente è la pulizia. Al ritorno da una chiamata Dispose su un abbonamento, l'osservatore non vedrà più alcun messaggio? Al momento dell'invio di un messaggio terminale nell'osservatore, la logica Dispose per l'abbonamento correlato verrà richiamata automaticamente? Entrambi sono non banali da correggere, quindi la nostra implementazione di base si occupa di ciò.

Un'altra ragione ha a che fare con il nostro CurrentThreadScheduler, assicurando che le chiamate di iscrizione possano essere asincrone quando si esegue su tale schedulatore.In sostanza, dobbiamo verificare se è necessario installare un trampolino sul thread corrente durante una chiamata a Iscriviti. Non ci aspettiamo che tutti sappiano questo e facciano la cosa giusta.

Nel tuo caso particolare - come notato da altri qui - stai costruendo praticamente un argomento. O semplicemente usa uno dei nostri soggetti, o avvolgilo per contenimento nel tuo tipo (ad es. Se vuoi che il lato "osservatore" di invio sia accessibile ad altre parti rispetto al lato "osservabile" ricevente).

+0

Che cosa consiglieresti se qualcuno volesse esporre un comportamento simile ad un evento, ma con i vantaggi semantici associati al fatto che un editore di eventi fornisca all'abbonato un token di "annullamento dell'iscrizione" che identifica l'abbonamento? – supercat

5

Un recente blog post del team Rx contiene tre motivi. Poiché è un lungo post ho copiato le parti rilevanti.

applicare il contratto

Observable.Create prende in un solo delegato che diventerà l'attuazione nucleo del metodo Abbonati sulla risultante IObservable attuazione. Facciamo qualcosa di intelligente attorno a questo delegato a e facciamo rispettare il contratto dell'osservatore, tra le altre cose (ecco perché non devi implementare l'interfaccia tu stesso, ).

involucro per monouso

La restituito monouso ha un piccolo involucro intorno, utilizzato per assicurare l'osservatore non sarà chiamato più dopo il ritorno dalla chiamata Dispose , anche se la lo scheduler potrebbe non essere ad un buon punto di sosta ancora. (Ancora un altro motivo non si dovrebbe mai implementare l'interfaccia IObservable a mano. Oh, e tra l'altro, c'è anche di più!) Dispose

automatica al termine

Il punto di interesse qui è il comportamento di auto-dispose applicato a alla sottoscrizione di origine dopo l'invio del downstream OnCompleted. (Questo è un altro motivo per cui implementazione manuale di IObservable è fortemente sconsigliato. Quando si utilizza Observable.Create, ci prendiamo cura di questo per voi.)

+0

Grazie, anche se mi piacerebbe pensare di aver pensato a tutti e tre nella mia ultima implementazione :) – TDaver

Problemi correlati