Add support for System.Threading.Lock by JakenVeina · Pull Request #2254 · dotnet/reactive
The basic idea behind IQbservable<T> is that it supports exactly the same API as IObservable<T>, but the output is effectively a description of whatever query you've written. So if you do this:
IObservable<int> src = GetSomeObservable(); IObservable<int> xs = src .Where(x => x > 0) .Select(x => x * 2);
then xs is a thing you can actually subscribe to that removes negative numbers, and then doubles everything else. But if we write the very similar:
IQbservable<int> xs = src .AsQbservable() .Where(x => x > 0) .Select(x => x * 2); Console.WriteLine(xs.Expression);
then we've basically got the same query but this time as an IQbservable<int>, and that means that this is a description of the observable source. Instead of just being a thing we can subscribe to, we can inspect this. That Console.WriteLine(xs.Expression); displays this:
System.Reactive.Linq.ObservableImpl.RangeRecursive.Where(x => (x > 0)).Select(x => (x * 2))
(The RangeRecursive there comes from the fact that in the example I'm testing this in, my GetSomeObservable() is returning Observable.Range(0, 10);. So that's really just the type of src here.)
So you can see that this thing knows that this is a Where clause followed by a Select clause. And if you were to inspect xs in the debugger, you'll see a DebugView property that looks like this:
.Call System.Reactive.Linq.Qbservable.Select(
.Call System.Reactive.Linq.Qbservable.Where(
.Constant<System.Reactive.ObservableQuery`1[System.Int32]>(System.Reactive.Linq.ObservableImpl.RangeRecursive),
'(.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>)),
'(.Lambda #Lambda2<System.Func`2[System.Int32,System.Int32]>))
.Lambda #Lambda1<System.Func`2[System.Int32,System.Boolean]>(System.Int32 $x) {
$x > 0
}
.Lambda #Lambda2<System.Func`2[System.Int32,System.Int32]>(System.Int32 $x) {
$x * 2
}
So the basic idea here is that an IQbservable<T> is a complete description of the subscription, one that can be inspected at runtime.
IQbservable<T> is to IObservable<T> as IQueryable<T> is to IEnumerable<T>. Systems like Entity Framework exploit the fact that an IQueryable<T> remembers exactly how the query was constructed—just like with the IQbservable<T> I've shown here, an IQueryable<T> would remember that it was built up as (say) a Where and a Select, and EF would then use that information to work out what SQL query to generate to be able to execute the logic that the query represents on a database.
The idea behind IQueryable<T> is that it could be used to support similar mechanisms. You could imagine a remote monitoring device presenting an IQueryable<T> API, and if that remote device were able to support local filtering, you could imagine this implementation of IQueryable<T> detecting when your query starts with a Where clause, and translating that into whatever format the remote device uses to express the filtering (in exactly the same way that Entity Framework translates a LINQ Where into a SQL WHERE clause.)
Admittedly, I'm not aware of any public libraries that actually do that. But it's a model we support, and it's also the basis of distributed query execution in Reaqtor (https://reaqtive.net/). So there are automated tests that check that the public API we define for IObservable<T> is fully matched by the one available for IQueryable<T>. And we have a tool in the repo that auto-generates the necessary code (the Homoiconicity tool). Although the tool is a bit cranky, and currently I have to manually fix the code it generates...you end up running it and then discarding most of what it changed, and keeping just the new bits. At some point I need to fix the tool so that we don't have to do that every time we add a new API feature.