Observable.Generate and List Enumeration

    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 xs = Observable.Generate(
        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 output by the compiler) is that there is a known bug in the C # compiler, as a result of which type inference is not friendly with named parameters. You can read more about this on stackoverflow or on connect-e . Yes, by the way, this thing was fixed in Visual Studio 11.

    The Generate method resembles a simple for loop , which consists of the same steps: initializing the initial value, exit condition from the loop, and changing the loop variable. And if we want to generate the usual sequence in memory, then we will use the cyclefor inside iterator block:

    static IEnumerable 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 (in the form of the IObservable of T interface ), which will independently notify of the arrival of new elements, then in the second case we get a pull sequence (in the form of an IEnumerable of T interface ), from which you need to “pull out” these elements with “pliers”.

    NOTE
    If it is very short, then the pull- model is such a model of the application’s interaction with the outside world, when the application plays the leading role. If you look at the IEnumerable interface, then it is the calling code that “controls” the flow of the element by calling the MoveNext method to go to the next. In push models, actions unfold differently: the external environment itself notifies the application of certain events (for example, the availability of new data), and the application only “responds” to them. You can read more about these models, about the dualism of the IEnumerable / IObservable interfaces , as well as about other features of the reactive extension library in the article: Reactive extensions and asynchronous operations .

    Because push and pull sequence interfaces ( IObservable of T and IEnumerable of T, respectively) are so similar, then the thought of transforming from one form of sequence into another can easily come to our head:

    int[] ai = new[] {1, 2, 3};
    IObservable 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 expectedly get:

    1
    2
    3

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

    List li = new List {1, 2, 3};
    IObservable 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 we either don’t 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, variable structures (after all, the enumerator changes its internal state) do not really fit in with the transmission by value. As a result of this, we are constantly trying to change the copy of the enumerator, and not the original enumerator that we passed in the Generate method .

    Variable structures are evil, and only the desire to optimize the code for the most common use case for enumerators has led BCL developers to make them structures, not classes. Since the most common use case for enumerators in C # 2.0 was to use it in a foreach loop, which did not suffer from problems with mutable structures, it was decided to save a few processor cycles and use a structure rather than a class for the enumerator.

    NOTE
    Details on why this was done this way can be found in one of Sasha Goldshtein 's posts , as well as Eric Lippert's answer to stackoverflow. Other problems of mutable significant types can be found in the article:On the dangers of mutable significant types .

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

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

    Also popular now: