Operator yield

class UserCollection { public static IEnumerable Power() { yield return "Hello world!"; } } 

The yield statement from .Net Reflector - transferred to C # and the Visual Studio

 class UserCollection { public static IEnumerable Power() { return new ClassPower(-2); } private sealed class ClassPower : IEnumerable<object>, IEnumerator<object>, IEnumerator, IDisposable { // Поля. private int state; private object current; private int initialThreadId; // Конструктор. public ClassPower(int state) { this.state = state; this.initialThreadId = Thread.CurrentThread.ManagedThreadId; } //private bool IEnumerator.MoveNext() // Так в Рефлекторе bool IEnumerator.MoveNext() { switch (this.state) { case 0: this.state = -1; this.current = "Hello world!"; this.state = 1; return true; case 1: this.state = -1; break; } return false; } IEnumerator<object> IEnumerable<object>.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) && (this.state == -2)) { this.state = 0; return this; } return new UserCollection.ClassPower(0); } IEnumerator IEnumerable.GetEnumerator() { // Так в Рефлекторе //return this.System.Collections.Generic.IEnumerable<System.Object>.GetEnumerator(); return (this as IEnumerable<object>).GetEnumerator(); } void IEnumerator.Reset() { throw new NotSupportedException(); } void IDisposable.Dispose() { } // Свойства. object IEnumerator<object>.Current { get { return this.current; } } object IEnumerator.Current { get { return this.current; } } } } 

I'm interested in the line and how the author comments

 this.initialThreadId = Thread.CurrentThread.ManagedThreadId; 

This line indicates synchronization and access to this collection as a shared resource. Why when working with a yield, the collection is perceived as a shared resource and is generally necessary in principle to work with threads?

See the picture as I understood the work of the yield operator and it can because all the yield operators put their value in this box, so maybe you need to work with threads? enter image description here

    1 answer 1

    Pay attention to the IEnumerable<object>.GetEnumerator() function.

    This object implements both IEnumerable and IEnumerator . A state of -2 means that the object is “fresh”, has just returned from a call to the Power function, and its enumeration has not yet begun. In this situation, when GetEnumerator immediately called in the same thread (and this happens in the overwhelming majority of cases: for example, when you call foreach (var x in Power()) ), for efficiency reasons you can return the same object (since he serves as enumerator too).

    But if enumeration has already passed, the internal state of the object may be damaged, for this case a new object is returned. Similarly, if enumeration is performed in another stream, then to avoid the need for synchronization, it is better to create a new object.

    Let us dwell on the last paragraph. If the IEnumerable suddenly goes to another thread, and the GetEnumerator() method is called from there, and the same method is called in the main thread at the same time, then without checking the thread id it may happen that both threads pass this.state == -2 simultaneously this.state == -2 , and get the same object! As a result, they will interfere with enumeration. In this case, we give “win” to the same thread that created the object.

    Thus, this is a special optimization for the usual case (selection of only one object); "Unusual" use cases are more complicated.


    More on this topic: Jon Skeet, C # in Depth. Iterator block implementation details: auto-generated state machines .