Convert event without EventArgs using Observable.FromEvent - c#

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?

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.

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);
});

Related

How Can I Unfold This Event to an Observable Sequence?

I'm trying to write an Add-In for Outlook, and one of the events I'm using is ItemsEvents_Event.ItemChange - and its handler's signature takes an Object as a parameter (the item that changed):
items.ItemChange += CalendarItems_ItemChange;
private void CalendarItems_ItemChange(object anItem) {...}
How would I use Observable.FromEvent or Observable.FromEventPattern to create an observable sequence from this event "stream" instead of attaching/detaching the event as usual?
You need to use the FromEvent conversion overload to tell Rx how it should interpret your event:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
In your case it would look like:
var source = Observable.FromEvent<ItemsEvents_ItemEventChangeHandler, object>(
emit => new ItemsEvents_ItemEventChangeHandler((obj) => emit(obj)),
h => items.ItemChange += h,
h => items.ItemChange -= h);
Similar answer

How to properly observe non-standard events?

I am new to Reactive Extensions, and dealing with a COM Library that has events defined like this:
public delegate void MyDelegate(int requestId, double price, int amount);
public event MyDelegate MyEvent;
How do I properly observe this? I tried using Observable.FromEvent() but as the event's parameters are not of type EventArgs I don't see how FromEvent() or FromEventPattern() is going to work.
My current workaround is to attach a custom delegate to the event then invoke a Subject.OnNext() but I am guessing that's not how I should do it.
Here's an example of my current workaround:
MyEvent += new MyDelegate((int requestId, double price, int amount) =>
{
Task.Run(() =>
{
var args = new MyArgs()
{
requestId = requestId,
price = price,
amount = amount,
};
this.mySubject.OnNext(args);
});
});
There is a special overload of FromEvent for it. It is a little goofy to get your head around but the function signature looks like:
IObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Func<Action<TEventArgs>, TDelegate> conversion,
Action<TDelegate> addHandler,
Action<TDelegate> removeHandler);
The conversion function is the important part here, basically you are telling Rx how your delegate maps to a concrete type.
In your scenario it ends up looking something like this:
Observable.FromEvent<MyDelegate, MyArgs>(
converter => new MyDelegate(
(id, price, amount) => converter(new MyArgs {
RequestId = id,
Price = price,
Amount = amount
})
),
handler => MyEvent += handler,
handler => MyEvent -= handler);
So what is all this doing? Internally, it is similar to what you are doing (I'll paraphrase what it does conceptually, since the implementation is slightly more complicated). When a new subscription is made, the conversion function will be invoked with observer.OnNext passed in as the converter argument. This lambda will return a new MyDelegate instance that wraps the conversion function that we provided ((id, price, amount) => ...). This is what is then passed to the handler => MyEvent += handler method.
After that each time the event is fired it will call our lambda method and convert the passed arguments into an instance of MyArgs which is then delivered to converter/observer.OnNext.
In addition, to all that magic it will also take care of cleaning up the event handlers when you are done with it, gracefully hand exceptions down stream and will manage the memory over head by sharing a single event handler across multiple observers.
Source code

Composing several Rx actions without side effects

How do I add (compose) more actions, e.g, updateIndicators, instead of the single action so that information flows without side effects?
quote =>
{
this.changeQuote(quote.S, quote.B, quote.A);
} // Add action here, e.g., UpdateIndicators()
var qu = Observable.FromEvent<ApiQuoteHandler, QuoteUpdate>(
emit => (_, s, b, a) => emit(new QuoteUpdate(s, b, a)),
handler => apiClient.QuoteUpdated += handler,
handler => apiClient.QuoteUpdated -= handler)
.Where(quote => (SymbolStrs.Contains(quote.S)))
.SubscribeOn(Scheduler.Default)
.Subscribe
(
quote =>
{
this.changeQuote(quote.S, quote.B, quote.A);
// I could put updateIndicators in here, but it doesn't feel Rx composable like?
}
);
public void changeQuote(string symbol, double bid, double ask)
{
}
public void updateIndicators(string symbol, double bid, double ask)
{
}
// more actions here
Well first obviously both of your actions will be nothing but sideeffects.
So the just either call subscribe 2 times:
var quoteUpdate =
Observable.FromEvent<ApiQuoteHandler, QuoteUpdate>(
emit => (_, s, b, a) => emit(new QuoteUpdate(s, b, a)),
handler => apiClient.QuoteUpdated += handler,
handler => apiClient.QuoteUpdated -= handler)
.Where(quote => (SymbolStrs.Contains(quote.S)));
var subscription1 =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.changeQuote(quote.S, quote.B, quote.A));
var subscription2 =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.updateIndicators(quote.S, quote.B, quote.A));
or subscribe to one Action that will just call one after the other (as you already guessed - don't see what's wrong with it):
public void DoBoth(string symbol, double bid, double ask)
{
changeQuote(symbol,bid,ask);
updateIndicators(symbol,bid,ask);
}
// ...
var subscription =
quoteUpdate
.SubscribeOn(Scheduler.Default)
.Subscribe (quote => this.DoBoth(quote.S, quote.B, quote.A));
remark:
right now you are only using Where and SubscribeOn from RX but you have quite a few lines of overhead. If you don't want to do more I would suggest just handling the event itself with a simple if instead of the .Where (of course dispatching to the UI Thread if you really have to) - it's way easier and you don't need the external dependency to RX then

Convert C# statements including NSubstitute code to VB.net

While reading the NSubstitute tutorial i convert the samples written in C# to VB.net to understand the functionality, but I need your support for these (unrelated) statements, which I can't convert despite all the care taken:
1.
calculator().Received().Add(1, Arg.Is(Of Integer)(function(x) new[] {-2,-5,-10}.Contains(x)))
2.
Note: foo is a derived object from an interface with a void method called "SayHello"
foo.When(x >= x.SayHello("World")).Do(x => counter++);
3.
calculator().When(x >= x.Add(-2, -2)).Do(x => { throw new Exception(); });
Note: engine is a derived object from this interface:
public interface IEngine {
event EventHandler Idling;
event EventHandler<LowFuelWarningEventArgs> LowFuelWarning;
event Action<int> RevvedAt;
}
4.
engine.Idling += (sender, args) => wasCalled = true;
5.
engine.Idling += Raise.EventWith(new object(), new EventArgs());
6.
engine.RevvedAt += rpm => revvedAt = rpm;
With your support I hope, I'm able to convert the remaining statements on my own.
Thank you in advance
Michael
It seems you want to convert Lambda's and adding event handlers.
Lambda from c#
.Where(x => x.Foo = 1)
.Do(x => x.Bar())
translates into
.Where(function(x) x.Foo = 1)
.Do(sub(x) x.Bar())
In VB.Net you have to take into account if the Labda is actually performing a function or a sub and code it accordingly.
Adding events in c#
engine.Idling += MyEventHandler
in VB.Net
AddHandler engine.Idling, AddressOf MyEventHandler
VB.Net lets u add the event like this. Removing an event is done by the keyword RemoveHandler
To add to Jeroen's answer, the general format for adding an event handler is:
AddHandler someObject.SomeEvent, SomeDelegate
You can use the AddressOf operator to create a delegate that refers to a named method but that is not the only way. A Lambda also creates a delegate so this:
engine.Idling += (sender, args) => wasCalled = true;
becomes this:
AddHandler engine.Idling, Sub(sender, args) wasCalled = True
Also, this line is not actually adding an event handler:
engine.RevvedAt += rpm => revvedAt = rpm;
so AddHandler won't work. I have never done it myself but I believe that you need to call Delegate.Combine for that:
engine.RevvedAt = [Delegate].Combine(engine.RevvedAt, Sub(rpm) revvedAt = rpm)

Error using TaskCompletionSource.TrySetResult()

This is a follow-up question to another SO question regarding the use of an async wrapper over an async callback function.
Here is the code as it stands (an excellent solution provided by #Servy):
static Task<ObservableCollection<MyResult>> GetMyDataAsync(Params p)
{
var tcs = new TaskCompletionSource<ObservableCollection<MyResult>>();
DoStuffClass stuff = new DoStuffClass();
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.Result);
stuff.LongDrawOutProcessAsync(p);
return tcs.Task;
}
So, my problem is with the LoadCompleted event; here is the signature:
public event EventHandler<MyArgs> LoadCompleted;
MyArgs contains a property called ResultCollection; however, changing the code like this does not work:
stuff.LoadCompleted += (args) => tcs.TrySetResult(args.ResultCollection);
In fact, I get the error:
'System.EventHandler<MyArgs>' does not take 1 arguments
Which I can see if correct from the signature; so how can I set the LoadCompleted result to the TaskCompletionSource?
EventHandler needs 2 arguments, the first is the instance that raised the event and the second is the event arguments. You need to specify both of them even if you only use one (args).
This should work:
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);
This should fix your problem
If you look at EventHandler<T> definition you will see it takes two arguments
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
So you need to pass two arguments in your assignment
stuff.LoadCompleted += (sender, args) => tcs.TrySetResult(args.Result);

Categories