Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert event without EventArgs using Observable.FromEvent

I'm struggling with converting the following event to an IObservable:

public delegate void _dispSolutionEvents_OpenedEventHandler();
event _dispSolutionEvents_OpenedEventHandler Opened;

The event comes from a library so I can't change it. The overload of IObservable.FromEvent that should do it has the following signature:

public static IObservable<Unit> FromEvent
    ( Action<Action> addHandler
    , Action<Action> removeHandler
    )

So I tried converting the event like this:

var opened = Observable.FromEvent
    ( h => _SolutionEvents.Opened += h
    , h => _SolutionEvents.Opened -= h
    );

But the compiler doesn't like _SolutionEvents.Opened += h and _SolutionEvents.Opened += h because

Cannot implicitly convert type 'System.Action' to 'EnvDTE._dispSolutionEvents_OpenedEventHandler'.

I don't think that I can just say_SolutionEvents.Opened += new _dispSolutionEvents_OpenedEventHandler(h) because then removal won't work because I have a different instance, right?

There is another overload of Observable.FromEvent with the following signature:

public static IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>
    ( Func<Action<TEventArgs>, TDelegate> conversion
    , Action<TDelegate> addHandler
    , Action<TDelegate> removeHandler
    )

This one allows to convert the action to an event handler, but it seems to only work with TEventArgs.

Is Rx missing an appropriate overload or am I missing something?

like image 718
Johannes Egger Avatar asked Mar 08 '16 06:03

Johannes Egger


2 Answers

You are running into a a type issue here. The _dispSolutionEvents_OpenedEventHandler type is not Action. It looks like the Action type, but it is not the Action type.

IMO this event does not conform to the .NET standards for events. Generally the delegate would match the pattern of taking a sender object parameter and an EventArg subclass of for the second parameter.

ie.

public delegate void _dispSolutionEvents_OpenedEventHandler(object sender, EventArgs e);

If you try to just attach an Action to the event you will find that fails too.

Action onOpened = ()=>Console.WriteLine("Opened");
_SolutionEvents.Opened += onOpened;  //CS0029 Cannot implicitly convert type 'System.Action' to '_dispSolutionEvents_OpenedEventHandler'

The compiler is smart enough to do some type inference if you do this;

_SolutionEvents.Opened+= () => Console.WriteLine("Opened");

but when you are using Rx, you are already typed into the Action type, so are effectively back at the previous issue above.

If the library owner was nice, the event would follow the normal sender/eventArgs pattern. Failing that, they would at least specify the delegate as just an Action instead of their own customer parameterless, void method. :-/

So, as the event you have doesn't meet the standard .NET patterns, you will need to provide Rx some more hand-holding (blame your library provider not Rx).

You could fight the FromEvent/FromEventPattern methods, but as your library is not in the spirit of an Event, I would suggest just going with the simple use of Observable.Create which at least keeps the code obvious what is happening and should allow the next user to better understand it.

Observable.Create<Unit>(obs =>
{
    _dispSolutionEvents_OpenedEventHandler handler = () => obs.OnNext(Unit.Default);

    _SolutionEvents.Opened += handler;
    return System.Reactive.Disposables.Disposable.Create(() => _SolutionEvents.Opened -= handler);
});
like image 66
Lee Campbell Avatar answered Oct 21 '22 18:10

Lee Campbell


This turns out that it is very easy to use the FromEvent pattern.

Just do this:

var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(
    h => () => h(Unit.Default),
    h => _SolutionEvents.Opened += h,
    h => _SolutionEvents.Opened -= h);

I've tested the observable with this code:

void Main()
{
    var _SolutionEvents = new Foo();

    var opened = Observable.FromEvent<_dispSolutionEvents_OpenedEventHandler, Unit>(h => () => h(Unit.Default), h => _SolutionEvents.Opened += h, h => _SolutionEvents.Opened -= h);

    opened.Subscribe(x => Console.WriteLine("Opened"));

    _SolutionEvents.OnOpened();
}

public delegate void _dispSolutionEvents_OpenedEventHandler();

public class Foo
{
    public event _dispSolutionEvents_OpenedEventHandler Opened;

    public void OnOpened()
    {
        this.Opened?.Invoke();
    }
}

It produces the following expected output:

Opened

It's worth noting that there is no IObservable interface, but only an IObservable<T> so you must return something. The trick here is to convert delegate void _dispSolutionEvents_OpenedEventHandler() into an IObservable<Unit> to make it work and that's what the h => () => h(Unit.Default) does.

like image 34
Enigmativity Avatar answered Oct 21 '22 19:10

Enigmativity