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
Related
There is a way to wrap an event as observable using Observable.FromEvent. E.g. this class:
class Generator<T>
{
event Action<T> onPush;
public IObservable<T> Items =>
Observable.FromEvent<T>(d => onPush += d, d => onPush -= d);
public void Push(T item) => onPush?.Invoke(item);
}
However, I haven't found a way to complete the observable also by an event - how can I do that?
Update:
To clarify what I mean, the class above produces IObservable<T> which is "endless" and never completes. I want to make it completed by another event, not to make another observable. So the question can be reduces to this:
How to make an arbitrary IObservable<T> completed prematurely, i.e. the OnCompleted notification to be called?
An observable represents a stream of notifications, or events. When an observable sources from an event, they are inherently endless. The observable connects to the event, referencing the object, so the object backing the event will never go out of scope. .NET/C# doesn't provide a way to indicate that an event will never be called again, so the observable directly connecting to the event is endless.
This is not uncommon; most event-based observables never have OnCompleted called explicitly, modelling the real world where it is quite hard to say definitively that something will never happen again.
However, this isn't a problem: Observables are meant to run infinitely, and cause no damage. An unsubscribed observable doesn't take up much resources. If you're not interested in an event-sourced observable, unsubscribe all subscriptions and you're fine.
One way to do this is with one of the Take operators, like the TakeUntil operator (as mentioned below). Try the following code (using your Generator class):
var g = new Generator<int>();
g.Items
.TakeUntil(i => i > 3)
.Subscribe(
i => Console.WriteLine($"OnNext: {i}"),
e => Console.WriteLine($"OnError: Message: {e.Message}"),
() => Console.WriteLine("OnCompleted")
);
g.Push(1);
g.Push(2);
g.Push(3);
g.Push(4);
g.Push(5);
g.Push(6);
Output:
OnNext: 1
OnNext: 2
OnNext: 3
OnNext: 4
OnCompleted
TakeUntil unsubscribes from the Items observable after there's a message with an integer larger than 3. This is why there's an OnCompleted, and no 5, 6 messages.
Also, as Enigmativity mentioned, your Generator<T> class is basically the same as Subject<T>, I suggest you use that.
Original answer:
Make another observable from the event, then use .TakeUntil:
class Generator<T>
{
event Action<T> onPush;
event Action<Unit> onCompleted;
public IObservable<T> Items =>
Observable.FromEvent<T>(d => onPush += d, d => onPush -= d)
.TakeUntil(Completion);
public IObservable<Unit> Completion =>
Observable.FromEvent<Unit>(d => onCompleted += d, d => onCompleted -= d);
public void Push(T item) => onPush?.Invoke(item);
public void Complete() => onCompleted?.Invoke(Unit.Default);
}
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);
});
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
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)
I have an attached behavior that used on a listbox, it should automatically select the first element in the list, if the list contains only one element.
The only way that I have found to hook the listbox when the list changes, is to use the listbox' itemcollections CollectionChanged event:
private static void ListenToItemsCollectionChange(ListBox listBox)
{
var collection = (INotifyCollectionChanged)listBox.Items;
collection.CollectionChanged += (sender, args) => SelectAndSetFocusToFirstElement(listBox);
}
The problem now, is that there is no way of unsubscribing from the event, which potentially leads to multiple invokations of SelectAndSetFocusToFirstelement( ).
The normal solution to this, is to not use lambdas. But then I would loose my listbox, which I need for selecting the first element.
Any suggestions on how this can be solved?
Full code
A Lambda is just a shortcut for a delegate, so you can rewrite the lambda as
NotifyCollectionChangedEventArgs collectionChangedDelegate = (sender, arg) =>
{SelectAndSetFocusToFirstElement(listBox)};
then you can add to the collection changed event
collection.CollectionChanged += collectionChangedDelegate
and remove
collection.CollectionChanged -= collectionChangedDelegate
I got a little bit confused what do You meand by "But then I would loose my listbox" ?
Maybe this solution will be sufficient
You could keep the event handler in temporary variable like that
EventHandler handler = (a, b) => { }; // You must use aproperiate delegate
collection.CollectionChanged += handler
and if You want to unsubscribe You could use -= handler