2013-03-05 13 views
7

Le estensioni reattive consentono di iscriversi facilmente ad un evento utilizzando Observable.FromEventPattern, ma non riesco a trovare nulla su come si potrebbe implementare un evento quando si dispone di un IObservable.come implementare un evento utilizzando reattivi estensioni

La mia situazione è questa: ho bisogno di implementare un'interfaccia che contiene un evento. Questo evento dovrebbe essere chiamato ogni volta che cambia un certo valore dell'oggetto, e per motivi di sicurezza del thread devo chiamare questo evento su un certo SynchronizationContext. Devo anche chiamare ogni gestore di eventi con il valore corrente alla registrazione.

public interface IFooWatcher 
{ 
    event FooChangedHandler FooChanged; 
} 

Ottenere un osservabile che fa quello che voglio è piuttosto facile con Rx utilizzando BehaviorSubject:

public class FooWatcher 
{ 
    private readonly BehaviorSubject<Foo> m_subject; 
    private readonly IObservable<Foo> m_observable; 

    public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue) 
    { 
     m_subject = new BehaviorSubject<Foo>(initialValue); 
     m_observable = m_subject 
      .DistinctUntilChanged() 
      .ObserveOn(synchronizationContext); 
    } 

    public event FooChangedHandler FooChanged 
    { 
     add { /* ??? */ } 
     remove { /* ??? */ } 
    } 
} 

Ora sto cercando un modo semplice per avere le add e remove funzioni iscriversi e cancellarsi dalla passata FooChangedHandler come Observer<Foo> su m_observable. Il mio attuale implementazione è simile a questo:

add 
    { 
     lock (m_lock) 
     { 
      IDisposable disp = m_observable.Subscribe(value); 
      m_registeredObservers.Add(
       new KeyValuePair<FooChangedHandler, IDisposable>(
        value, disp)); 
     } 
    } 

    remove 
    { 
     lock (m_lock) 
     { 
      KeyValuePair<FooChangedHandler, IDisposable> observerDisposable = 
       m_registeredObservers 
        .First(pair => object.Equals(pair.Key, value)); 
      m_registeredObservers.Remove(observerDisposable); 
      observerDisposable.Value.Dispose(); 
     } 
    } 

Tuttavia, spero di trovare una soluzione più facile, perché ho bisogno di implementare alcuni di questi eventi (di differenti tipi di gestore). Ho provato a rollare la mia soluzione generica, ma crea alcuni problemi aggiuntivi che devono essere risolti (in particolare, come si lavora genericamente con un delegato che accetta un parametro di T), quindi preferirei trovare una soluzione esistente per i bridge il divario in questa direzione - proprio come FromEventPattern fa il contrario.

risposta

2

Si potrebbe fare questo:

public event FooChangedHandler FooChanged 
{ 
    add { m_observable.ToEvent().OnNext += value; } 
    remove { m_observable.ToEvent().OnNext -= value; } 
} 

Tuttavia, sulla rimozione, penso che forse si può solo voler disporre della sottoscrizione ... o forse ottenere l'azione da ToEvent() e negozio che, come un membro. Non testato.

MODIFICA: Sarà necessario utilizzare Azione invece di un delegato FooChangedHandler, tuttavia.

MODIFICA 2: Ecco una versione testata. Suppongo che tu debba usare FooChangedHandler, comunque, dato che hai un gruppo di questi handler preesistenti?

void Main() 
{ 
    IObservable<Foo> foos = new [] { new Foo { X = 1 }, new Foo { X = 2 } }.ToObservable(); 
    var watcher = new FooWatcher(SynchronizationContext.Current, new Foo { X = 12 }); 
    watcher.FooChanged += o => o.X.Dump(); 
    foos.Subscribe(watcher.Subject.OnNext); 
} 

// Define other methods and classes here 

//public delegate void FooChangedHandler(Foo foo); 
public interface IFooWatcher 
{ 
    event Action<Foo> FooChanged; 
} 

public class Foo { 
    public int X { get; set; } 
} 
public class FooWatcher 
{ 

    private readonly BehaviorSubject<Foo> m_subject; 
    public BehaviorSubject<Foo> Subject { get { return m_subject; } } 
    private readonly IObservable<Foo> m_observable; 

    public FooWatcher(SynchronizationContext synchronizationContext, Foo initialValue) 
    { 
     m_subject = new BehaviorSubject<Foo>(initialValue); 

     m_observable = m_subject 
      .DistinctUntilChanged(); 
    } 

    public event Action<Foo> FooChanged 
    { 
     add { m_observable.ToEvent().OnNext += value; } 
     remove { m_observable.ToEvent().OnNext -= value; } 
    } 
} 
+0

Grazie, non avevo visto 'ToEvent()' prima! Questo è già un grande pezzo del puzzle. La conversione tra 'Azione ' e 'FooChangedHandler' è ancora un problema (sì, purtroppo siamo bloccati con questi).Problema: i delegati sono uguali se hanno lo stesso obiettivo e metodo, ma questa uguaglianza raggiunge solo un livello - se si dispone di un delegato e lo si avvolge in due istanze separate 'Azione ', saranno uguali, ma se si avvolgono entrambi quelli 'Azione ' istanze in un'altra 'Azione ' ognuna, * quelle * non * non * saranno uguali. Tuttavia, penso di aver già visto una soluzione a questo ... – Medo42

+1

Ah sì, ecco: http://stackoverflow.com/a/9290684/575615 Lo proverò domani, grazie ancora! – Medo42

+0

Tutto funziona come previsto ora. L'utilizzo di una soluzione come quella sopra indicata per la conversione dei delegati in "Azione " mi ha permesso di scrivere una soluzione generica breve e dolce che funziona internamente con 'ToEvent()'. Non c'è molto per quella classe, ma volevo raggruppare 'Subject' e' IEventSource' in un unico oggetto. – Medo42

0

Dato che si stanno già miscelando i confini tra codice reattivo e codice più normale, è possibile eseguire una versione meno reattiva. Per iniziare è sufficiente dichiarare un normale modello evento

public event FooChangedHandler FooChanged; 

protected void OnFooChanged(Foo) 
{ 
    var temp = FooChanged; 
    if (temp != null) 
    { 
    temp(new FooChangedEventArgs(Foo)); 
    } 
} 

e poi semplicemente collegare il osservabile ad esso nel costruttore

m_Observable.Subscribe(foo => OnFooChanged(foo)); 

Non è molto Rx ma è incredibilmente semplice.

+0

Puoi omettere 'OnFooChanged' completamente assegnando un' delegate {} 'a' FooChanged' proprio all'inizio, comprimendo questa soluzione su due righe. Tuttavia, questo approccio non soddisfa i miei requisiti, perché i nuovi abbonati all'evento non ricevono lo stato corrente. Inoltre, poiché il mio 'SyncrhonizationContext' essenzialmente post su un ciclo di eventi, i nuovi abbonati possono ricevere modifiche che (usando i termini del modello di memoria Java) sono avvenute, prima che la sottoscrizione fosse stabilita. Questo probabilmente non è un problema, ma preferirei evitarlo. – Medo42

Problemi correlati