One of the new changes in reactive extension 2.0 and above is the optimization in “group by” clause
Browsing through Rx discussion forum, I learned a neat little trick that makes use of the above enhancement, this is a simple technique that lets you apply different processing logic for each item generated on the observable stream, all without comparing predicates (well comparison is done only once), neither do you need to maintain any dictionary etc, Reactive Extension takes care of it, all.
Let’s see this with an example: Say we have a stream of financial instruments (ETF, Stock, and Option) and we want to process them differently depending on their type.
Unoptimized way (with if)
Use an event, when the item is added, invoke the callback and process the new item
callback(item)
{
if(item == Item.Stock) ProcessStock(item);
if(item == Item.Etf) ProcessEtf(item)
if(item == Item.Option) ProcessOption(item)
}
I know we could have used strategy pattern and totally avoid the if(s) and just do with ProcessItem(item), but the intent here is to show that we are invoking totally different function for each item (it is a bad example, but hope you got the point)
Unoptimized way (Reactive Extension)
observable.Where(Item == Item.Stock).Subscribe(x => ProcessStock(x));
observable.Where(Item == Item.Etf).Subscribe(x => ProcessEtf(x));
observable.Where(Item == Item.Option).Subscribe(x => ProcessOption(x));
This is un-optimized cause when “stock” item is generated, we would still process “Item == Item.Etf” and “Item == Item.Option” predicates.
Optimized way (Reactive Extension)
var connectableObservable = observable.GroupBy(x =>x.Item).Publish();
connectableObservable.Where(x => x.Key == Item.Stock).Take(1).SelectMany(y => y).Subscribe(s => ProcessStock(s));
connectableObservable.Where(x => x.Key == Item.Option).Take(1).SelectMany(y => y).Subscribe(s => ProcessOption(s));
connectableObservable.Where(x => x.Key == Item.Etf).Take(1).SelectMany(y => y).Subscribe(s => ProcessEtf(s));
connectabeObservable.Connect();
As GroupBy internally maintains subject for each group (Look at the GroupBy source code, the operator maintains a dictionary of type “Dictionary<TKey, ISubject<TElement>), when a new item is generated, the group by operator finds the unique key for the item via the selector functor (in our above example the select functor is “x.item” passed to the group by clause), from the key, the corresponding unique subject is retrieved to which this new item is pushed, thus for every item generated, only one subject is notified.
In essence only one predicate is applied once the grouped observables is setup, this is a nice optimization which totally avoids duplicate matching.
If we have a handful of predicates then “where” clause would be just fine, but if we have complex or way too many predicates to compare then group by operator is the way to go.
This technique can also be use to implement your own command pattern.
