views:

215

answers:

4

Here's what I mean. Suppose I'm working with an API that exposes events, but these events do not follow the standard EventHandler or EventHandler<TEventArgs> signature. One event might look like this, for instance:

Public Event Update(ByVal sender As BaseSubscription, ByVal e As BaseEvent)

Now, typically, if I want to get an IObservable<TEventArgs> from an event, I can just do this:

Dim updates = Observable.FromEvent(Of UpdateEventArgs)( _
    target:=updateSource, _
    eventName:="Update" _
)

But this doesn't work, because the Update event is not an EventHandler<UpdateEventArgs> -- in fact, there is no UpdateEventArgs -- it's basically just its own thing.

Obviously, I could define my own class deriving from EventArgs (i.e., UpdateEventArgs), write another class to wrap the object providing the Update event, give the wrapper class its own Update event that is an EventHandler<UpdateEventArgs>, and get an IObservable<UpdateEventArgs> from that. But that's an annoying amount of work.

Is there some way to create an IObservable<[something]> from a "non-standard" event like this, or am I out of luck?


UPDATE: From Jon Skeet's answer, I'm nudged in the direction of the following overload of Observable.FromEvent:

Function FromEvent(Of TDelegate, TEventArgs As EventArgs)( _
    conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _
    addHandler As Action(Of TDelegate), _
    removeHandler As Action(Of TDelegate) _
) As IObservable(Of IEvent(Of TEventArgs))

I have to admit, though, I'm having trouble wrapping my head around that Func(Of EventHandler(Of TEventArgs), TDelegate) part. It seems backwards to me (?). Obviously, there's just something I'm missing...

Anyway, in case it helps, I think this is what the equivalent C# code would look like (I'll be perfectly honest: I'm not sure about this. Even though I generally prefer C# myself, this code is the work of one of my colleagues, who writes primarily in VB.NET; and VB.NET permits multiple syntaxes for declaring events):

// notice: not an EventHandler<TEventArgs>
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e);

// not 100% sure why he did it this way
public event UpdateEventHandler Update;

The tricky part here is that it seems that some kind of class deriving from EventArgs is necessary, no matter what. In the API I'm working with, there is no such class. So, bare minimum, I'll have to write one. But that should be fairly trivial (basically one property: BaseEvent).

In the end, I'm assuming the code required for this overload would look something like this in C#:

var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>(
    // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>)
    handler => (sender, e) => handler(sender, new UpdateEventArgs(e)),
    // addHandler (Action<UpdateEventHandler>)
    handler => updateSource.Update += handler,
    // removeHandler (Action<UpdateEventHandler>)
    handler => updateSource.Update -= handler
);

First of all: do I even have this straight? Second of all: am I correct in saying that using VB 9, there's really no way to accomplish the above without writing my own methods?

It almost feels to me like I'm approaching this problem from the entirely wrong angle to begin with. But I'm really not sure.

+1  A: 

You may be able to use this signature:

Public Shared Function FromEvent(Of TDelegate, TEventArgs As EventArgs) ( _
    conversion As Func(Of EventHandler(Of TEventArgs), TDelegate), _
    addHandler As Action(Of TDelegate), _
    removeHandler As Action(Of TDelegate) _
) As IObservable(Of IEvent(Of TEventArgs))

Here, TDelegate would be the delegate type of the event (which I can't tell immediately from your declaration - C# event declarations don't look quite like that, and I'm afraid I don't know enough VB to decypher it, but I'm sure there's a delegate type there somewhere). TEventArgs would be a type for the event argument (BaseEvent should do it here, I think). You'd need to provide a converter from an EventHandler(Of BaseEvent) to your delegate type - this would probably just be a lambda expression to call the given event handler with the arguments passed into it. The add and remove actions would be the normal event subscription code - but expressed as delegates.

Unfortunately my VB isn't good enough to express all of this neatly - or indeed to know how much is easily available in VB 9 or 10. I know how it would all look in C#... if you could give me a short but complete example in C# which just left me to fill in the subscription bit, I could certainly do that...

Jon Skeet
@Jon: This definitely seems like a promising option. I've updated my question with what I *believe* is the C# equivalent of the VB.NET code I originally posted. If you get a chance to read my update: does my C# version of the hypothetical code I'd need to write look correct to you? I know you said you're not very familiar with VB, so I guess I'm on my own when it comes to working out that part...
Dan Tao
+1  A: 

Perhaps you could just add your own implementation for the custom event signature?

public interface ICustomEvent<TSource, TArgs>
{
    public TSource Source { get; }
    public TArgs EventArgs { get; }
}

public interface CustomEvent<TSource, TArgs> : ICustomEvent<TSource, TArgs>
{
    public TSource Source { get; set; }
    public TArgs EventArgs { get; set; }
}

public static class ObservableEx
{
    public static IObservable<ICustomEvent<TSource, TArgs>> FromCustomEvent(
        Action<Action<TSource, TArgs>> addHandler, 
        Action<Action<TSource, TArgs>> removeHandler)
    {
        return Observable.CreateWithDisposable(observer =>
            {
                Action<TSource, TArgs> eventHandler = (s,a) => 
                    observer.OnNext(new CustomEvent<TSource,TArgs>(s,a));

                addHandler(eventHandler);

                return Disposable.Create(() => removeHandler(eventHandler));
            });
    }
}

Then you can use it as:

var observable = ObservableEx.FromCustomEvent<BaseSubscription,BaseEvent>(
    h => updateSource.Update += h,
    h => updateSource.Update -= h
);
Richard Szalay
This looks quite promising. I'm going to check this out.
Dan Tao
+1  A: 

You can also just do this the lazy way, if updateSource never goes away:

var observable = new Subject<BaseEvent>();
updateSource.Update += (o,e) => observable.OnNext(e);

Jon's plan is probably a better one though, but Subjects can help you out.

Paul Betts
A: 

For future reference, this is an example of using the conversion overload of FromEvent, using the FileSystemEventHandler as an example:

    Dim createWatcher As New FileSystemWatcher With {.Path = "C:\Temp", .EnableRaisingEvents = True}
    Dim foo = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
        Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)),
        Sub(ev) AddHandler createWatcher.Created, ev,
        Sub(ev) RemoveHandler createWatcher.Created, ev)

    Dim changedEv = Observable.FromEvent(Of FileSystemEventHandler, FileSystemEventArgs)(
        Function(ev) New FileSystemEventHandler(Sub(o, e) ev(o, e)),
        Sub(ev) AddHandler createWatcher.Changed, ev,
        Sub(ev) RemoveHandler createWatcher.Changed, ev)

    foo.Subscribe(Sub(e) Console.WriteLine("File {0} created.", e.EventArgs.Name))
    changedEv.Subscribe(Sub(e) Console.WriteLine("File {0} changed.", e.EventArgs.Name))
Richard Hein