How can I get IObservable <T> in Rx from a "non-standard" event?
Here is what I mean. Suppose I work with an API that provides events, but these events do not match the standard signature of EventHandler or EventHandler<TEventArgs> . One event might look like this:
Public Event Update(ByVal sender As BaseSubscription, ByVal e As BaseEvent) Now, as a rule, if I want to get IObservable<TEventArgs> from an event, I can simply do this:
Dim updates = Observable.FromEvent(Of UpdateEventArgs)( _ target:=updateSource, _ eventName:="Update" _ ) But this does not work, because the Update event is not an EventHandler<UpdateEventArgs> - in fact, there is no UpdateEventArgs - this is basically just its own thing.
Obviously, I could define my own class derived from EventArgs (i.e. UpdateEventArgs ), write another class to wrap the object that provides the Update event, provide the wrapper class its own Update event, which is a EventHandler<UpdateEventArgs> and get from its IObservable<UpdateEventArgs> . But this is an annoying amount of work.
Is there a way to create an IObservable<[something]> from a "non-standard" event like this, or am I out of luck?
UPDATE . From John Skeet's answer, they are pushing me toward the next Observable.FromEvent overload:
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 that I'm having trouble wrapping this part of Func(Of EventHandler(Of TEventArgs), TDelegate) . It seems to me the opposite to me (?). Obviously I'm just missing ...
In any case, in case this helps, I think it will look like equivalent C # code (I will be completely honest: I'm not sure about that. Even if I generally prefer C # myself, this code is the work of one of of my colleagues, who writes mainly on VB.NET, and VB.NET allows 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 hard part here is that it seems that some class derived from EventArgs is necessary, no matter what. There is no such class in the API I'm working with. So minimum, I have to write one. But this should be pretty trivial (basically one property: BaseEvent ).
In the end, I assume that the code needed for this overload will 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 it right? Secondly, I correctly say that, using VB 9, there is no way to perform the above without writing your own methods?
It almost seems to me that I'm starting with this problem from a completely wrong angle. But I'm really not sure.
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 like:
var observable = ObservableEx.FromCustomEvent<BaseSubscription,BaseEvent>( h => updateSource.Update += h, h => updateSource.Update -= h ); You can 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 will be the type of event delegation (which I cannot immediately tell from your announcement). The C # event declaration does not look exactly like that, and I'm afraid I donβt know enough VB to decrypt it, but I'm sure there is somewhere a delegate type). TEventArgs will be the type for the event argument ( BaseEvent should do it here, I think). You will need to provide a converter from EventHandler(Of BaseEvent) to your delegate type - this will probably just be a lambda expression to invoke the handler of the given event with the arguments passed to it. Add and delete actions will be the normal event subscription code, but will be presented as delegates.
Unfortunately, my VB is not good enough to put it all neatly - or really know how easily available in VB 9 or 10. I know how it all will look in C # ... if you could give me a short, but full example in C # that just left me to fill out a subscription bit, I could do this ...
You can also just do it in a lazy way if updateSource never quits:
var observable = new Subject<BaseEvent>(); updateSource.Update += (o,e) => observable.OnNext(e); Jon plan is probably better, but actors can help you.
For future reference, this is an example of using FromEvent overload using 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)) var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>( // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>) handler => (BaseSubscription sender, BaseEvent e) => handler.Invoke(sender, new UpdateEventArgs(e)), // addHandler (Action<UpdateEventHandler>) handler => updateSource.Update += handler, // removeHandler (Action<UpdateEventHandler>) handler => updateSource.Update -= handler ); Make sure UpdateEventArgs ctor accepts the BaseEvent argument.
I have the same nightmare because I work with an interop API that uses non-standard .net events. I am new to many things, including Generics, Funcs, Actions, Observables and Rx, so I believe that my experience in understanding such things will have some value.
We can plunge into Func(Of EventHandler(Of TEventArgs), TDelegate) conversion , having understood where it is used.
But first, we need to understand the generics signature of the FromEvent method.
The following is the signature of the FromEvent extension method function in vb:
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)) But I really don't use vb, so I have to resort to a C # signature:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>( Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler); Analyze C # signature line by line.
Note. I will include data types in lambda expressions to distinguish between standard .net events and non-standard events.
First part:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>( This suggests that the FromEvent function accepts TDelegate and TEventArgs As EventArgs as INPUT. Note that OUTPUT IObservable will also be of type TEventArgs . So, you are right when you said that you need a class that will wrap TDelegate data. I don't know which version I'm using, but it allows me to use any class, even if it does not inherit from EventArgs . In case vb does not allow you, this is trivial work, as it simply adds :EventArgs to the class (Inherits from vb?). Let me apply this to your problem:
Your C # assumptions:
// notice: not an EventHandler<TEventArgs> public delegate void public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e); // class to wrap the data from the above delegate public class UpdateEventArgs:EventArgs {...} Applies to the first line:
var updates = FromEvent<UpdateEventHandler, UpdateEventArgs>( The second part of:
Then we have three conversion inputs, addhandler and removehandler :
Func<Action<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler); We know that addhandler and removehandler simply add and remove a delegate from the event. Let's do these first two.
// addHandler (Action<UpdateEventHandler>) handler => updateSource.Update += handler, // removeHandler (Action<UpdateEventHandler>) handler => updateSource.Update -= handler Now apply our type to the complex part:
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion, This function accepts the input Action<UpdateEventArgs> , and the delegate - UpdateEventHandler . Let assign it to a variable called conversion
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion = (handlerOfUpdateEventArgs) => { UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) => handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e)); return handler; }; To better understand what this does, let's see how we attach an event handler to a standard .net event:
someObject.SomeEvent += (object sender,EventArgs args) => { ... }; Now we will consider your non-standard event .net and UpdateEventHandler :
public delegate void UpdateEventHandler(BaseSubscription sender, BaseEvent e); updateSource.Update += (BaseSubscription sender, BaseEvent e) => { ... }; If you look at the signature of the conversion function, it returns the UpdateEventHandler delegate. This means that we can use conversion to attach to the Update event. But before we can do this, the conversion function requires an Action<UpdateEventArgs> input before it can be used. Now do this:
//EventHandler<EventArgs> similarity. Action<UpdateEventArgs> actionUpdateEventArgs = (UpdateEventArgs e) => { //This is were you put your code like in a standard.net event //This is also probably where the Observable.FromEvent() puts //wiring necessary to make it into an IObservable<UpdateEventArgs> }; Now that we have all the parts we need, we can use conversion similarly to the event handler.
updateSource.Update += conversion(actionUpdateEventArgs); The code inside actionUpdateEventArgs will be called every time Update is created.
I hope this was enough to understand the parameter Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion .
Finally, here is how you will use the FromEvent() method:
Func<Action<UpdateEventArgs>, UpdateEventHandler> conversion = (handlerOfUpdateEventArgs) => { UpdateEventHandler handler = (BaseSubscription sender, BaseEvent e) => handlerOfUpdateEventArgs(sender, new UpdateEventArgs(e)); return handler; }; var updates = Observable.FromEvent<UpdateEventHandler, UpdateEventArgs>( // conversion (Func<EventHandler<UpdateEventArgs>, UpdateEventHandler>) conversion, // addHandler (Action<UpdateEventHandler>) handler => updateSource.Update += handler, // removeHandler (Action<UpdateEventHandler>) handler => updateSource.Update -= handler ); This is how I realized that he is a beginner, so I hope it will be useful.