📜 ⬆️ ⬇️

Observable.Generate and list listings

In the library of reactive extensions (aka Rx - Reactive Extensions) there is a helper method Observable . Generate , which allows you to generate simple observable sequences.

IObservable<string> xs = Observable.Generate<int, string>( initialState: 0, //   condition: x => x < 10, //    iterate: x => x + 1, //   resultSelector: x => x.ToString() //      ); xs.Subscribe(x => Console.WriteLine("x: {0}", x)); 


NOTE
Pay attention to the explicit indication of the parameters of the generalized method. The reason for this strange behavior (after all, the generalized parameters of the methods should be easily displayed by the compiler) is that in the C # compiler there is a known bug, as a result of which the type inference is not friendly with the named parameters. Read more about it at stackoverflow or connect-e . Yes, by the way, this case was fixed in Visual Studio 11.

The Generate method resembles a simple for loop, which consists of the same steps: initializing the initial value, conditions for exiting the loop, and changing the loop variable. And if we want to generate the usual sequence in memory, then we will use the for loop inside the block of iterators:
')
 static IEnumerable<string> GenerateSequence() { for ( int x = 0; //initialState x < 10; // condition x = x + 1 // iterate ) { yield return x.ToString(); // resultSelector } } static void Main(string[] args) { var xs = GenerateSequence(); foreach (var x in xs) Console.WriteLine("x: {0}", x); } 


Both versions of the code are equivalent, but if in the first case a push sequence is created (as an IObservable of T interface) that will independently notify you when new items arrive, in the second case we get a pull- sequence (as an IEnumerable of T interface), from which you need "pliers" to pull these elements.

NOTE
If it is very short, then the pull model is such a model of application interaction with the outside world, when the application plays the leading role. If you look at the IEnumerable interface, it is the calling code that “controls” the element by the thread, calling the MoveNext method to go to the next. In the push model, actions take place differently: the external environment itself notifies the application about some events (for example, the availability of new data), and the application only “responds” to them. In more detail about these models, about dualism of interfaces IEnumerable / IObservable , and also about other opportunities of library of jet expansions it is possible to read in article: Reactive expansions and asynchronous operations .

Since the interfaces of push and pull sequences ( IObservable of T and IEnumerable of T , respectively) are so similar, we can easily think of converting from one form of the sequence to another:

 int[] ai = new[] {1, 2, 3}; IObservable<int> oi = Observable.Generate( /*initialState:*/ ai.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current ); oi.Subscribe(i => Console.WriteLine("i: {0}", i)); 


In general, nothing criminal, and when executing this code, we expect to receive:

one
2
3

But what if instead of an array we want to do the same with the list:

 List<int> li = new List<int> {1, 2, 3}; IObservable<int> oi = Observable.Generate( /*initialState:*/ li.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current ); oi.Subscribe(i => Console.WriteLine("i: {0}", i)); 


And, quite naturally, in this case we get ... an infinite sequence of zeros.

NOTE
Yes, I know that the easiest way to convert a simple sequence into an observable sequence is to use the Observable extension method . ToObservable , which "extends" the IEnumerable interface. But suppose that we either do not know about it, or we need more complex logic available in the Generate method.

The reason for this behavior is that the enumerator of the List of T class (as well as most other BCL collections) is a structure, not a class. And, as we know, changeable structures (after all, the enumerator changes its internal state) do not really fit in with passing by value. As a result of this, we are constantly trying to change the copy of the enumerator, and not the original enumeror, which we transmitted in the Generate method.

Modifiable structures are evil, and only the desire to optimize the code for the most common scenario of using enumerators led to the fact that the developers of BCL made them structures, not classes. Since the most common scenario of using enumerators in C # 2.0 was to use it in the foreach loop, which did not suffer from problems with modifiable structures, it was decided to save a few processor cycles and use the enumerator for the structure, not class.

NOTE
Details about why this was done in this way can be found in one of the posts of Sasha Goldshtein, as well as in Eric Lippert’s response to stackoverflow. About other problems of changeable significant types can be read in the article: About the harm of changeable significant types .

This problem is solved quite simply: to do this, it is enough to bring the initial enumerator value to the interface, which will lead to its packaging and subsequent modification of the “common” variable located in the managed heap, and not to a copy change:

 IObservable<int> oi = Observable.Generate( /*initialState:*/ (IEnumerator<int>)li.GetEnumerator(), /*condition:*/ e => e.MoveNext(), /*iterate:*/ e => e, /*resultSelector:*/ e => (int)e.Current ); 

Source: https://habr.com/ru/post/132314/


All Articles