Composing several Rx actions without side effects - c#

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

Related

Complete IObservable by event

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

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

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

Use Reactive Extensions to simulate Click on UIElement

In WPF, I want to be able to use the mouse events with reactive extensions to create an observable for a UIElement that works like a Click event. There's plenty of examples of using this to create drag/drop behavior, but I can't find anything for just a simple click.
I'm anticipating it'll involve observables on MouseLeftButtonDown, MouseLeftButtonUp, MouseLeave, and MouseEnter. But I'm unsure what combination of Merge, SelectMany, TakeUntil, or TakeWhile I need to use. In trying to wrap it all up in an extension, here is what I have so far:
public static IDisposable GetClick(this UIElement item, Action clickAction)
{
var obs1 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeftButtonDown += h,
h => item.MouseLeftButtonDown -= h);
var obs2 = Observable.FromEventPattern<MouseButtonEventHandler, MouseButtonEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeftButtonUp += h,
h => item.MouseLeftButtonUp -= h);
var obs3 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseLeave += h,
h => item.MouseLeave -= h);
var obs4 = Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
h => (s, e) => h(s, e),
h => item.MouseEnter += h,
h => item.MouseEnter -= h);
var finalObs = ???
return finalObs.Subscribe(x => clickAction.Invoke());
}
The following seems to work, but I suspect that it's possible to do it in a neater way.
var click = mouseEnter
.SelectMany(_ => mouseDown.TakeUntil(mouseLeave))
.SelectMany(_ => mouseUp.TakeUntil(mouseLeave).Take(1));
I've renamed finalObs to click, obs1 to mouseDown, obs2 to mouseUp...
EDIT: Added Take(1) to fix the flaw pointed out by Enigmativity
EDIT(2):
Here is another solution that I like more.
You'll need to add a .Select(_ => "U") to the definition of mouseUp, .Select(_ => "D") to mouseDown...
var click = Observable.Merge(mouseDown, mouseUp, mouseLeave, mouseEnter)
.Scan((s, c) => c == "L" ? "" : s + c) // Create a string of the events, reset on mouseLeave
.Where(s => s.Length >= 2 && s.Substring(s.Length - 2) == "DU");
After thinking about it, it's impossible to get exactly correct behavior in the case where the user mouse downs over the item, then moves outside of the item, then moves back and mouse ups. This is because you don't get mouse ups when not over the item, so you can't be sure they didn't mouse up, then mouse down while outside.
The correct way is to use this.CaptureMouse and this.ReleaseMouseCapture which solves some of the problems in the accepted answer to do with detecting the mouse leaving and returning. A full (untested) solution using ReactiveUI to bind the events is.
// Create a factory for capturing the mouse and and releasing it as an
// IDisposable compatible with Observable.Using
Func<IDisposable> captureDisposable = () => {
this.CaptureMouse();
return Disposable.Create(()=>this.ReleaseMouseCapture());
};
// Capture the mouse and then release it on mouse up
var up = Observable.Using
( captureDisposable
, capture => this.Events().PreviewMouseUp.Take(1)
);
// Create the click event
var click = this.Events().PreviewMouseDown.Select(e=>up).Switch();

Categories