August 26, 2021

Exposing C# Events using Reactive Extensions

There is a way to convert existing C# events to an IObservable<>. There is a good article here on this

public class SimpleEventSource
{
  public event EventHandler<MyEventArgs> SimpleEvent;
     
  // Normally this would be private (event is triggered internally) 
  // but for test purposes we make it public
  public void FireSimpleEvent(MyEvent myevent)
  {
    EventHandler<MyEventArgs> tmp = SimpleEvent;
    if (tmp != null)
    {
      tmp(this, new MyEventArgs(myEvent));
    }
  }   
 
  // This returns an IObservable (effectively an event pulisher)
  public IObservable<EventPattern<MyEventArgs>> MyEventPublisher
  {
    get
    {       // To consume SimpleEvent as an IObservable:
        IObservable<EventPattern<MyEventArgs>> eventAsObservable = Observable.
            FromEventPattern<MyEventArgs>(
              ev => SimpleEvent += ev,
              ev => SimpleEvent -= ev);
        return eventAsObservable;
    }
  }

}

Note that MyEventArgs is something derived from EventArgs that allows access to the "MyEvent" instance, see below. Here is a tester:

public void TestSimpleEventSource()
{
    var ses = new SimpleEventSource();
    ses.FireSimpleEvent(new MyEvent("1st event")); // This one is missed

    using (var subscription = ses.MyEventPublisher.Subscribe(
        ev => Console.WriteLine(ev.EventArgs.MyEvent.Text + " fired")))
    {
        ses.FireSimpleEvent(new MyEvent("2nd event"));
        ses.FireSimpleEvent(new MyEvent("3rd event"));               
    }

    Console.WriteLine("Subscription cancelled");
}
and the output
2nd event fired
3rd event fired
Subscription cancelled

Replacing C# Events using Reactive Extensions

Lets take this one step further, and completely replace the C# event with a reactive style event publisher

internal class SimpleEventSource
{
    private Subject<MyEvent> _myEventSubject = new Subject<MyEvent>();

    public IObservable<MyEvent> MyEventPublisher => this._myEventSubject.AsObservable();

    public void FireSimpleEvent(MyEvent myEvent) => this._myEventSubject.OnNext(myEvent);
}
And the same test but using this class:
public void TestSimpleEventSource2()
{
    var ses = new SimpleEventSource2();
    ses.FireSimpleEvent(new MyEvent("1st event"));

    using (var subscription = ses.MyEventPublisher.Subscribe(
        ev => Console.WriteLine(ev.Text + " fired")))
    {
        ses.FireSimpleEvent(new MyEvent("2nd event"));
        ses.FireSimpleEvent(new MyEvent("3rd event"));
    }

    Console.WriteLine("Subscription cancelled");
}

Here is the code for the simple event classes if it makes things clearer

public class MyEvent
{
    public MyEvent(string text)
    {
        Text = text;
    }
    public string Text { get; private set; }
}

public class MyEventArgs : EventArgs
{
    public MyEventArgs(MyEvent myEvent)
        : base()
    {
        MyEvent = myEvent;
    }
    public MyEvent MyEvent { get; private set; }
}

No comments: