How does foreach work if I put in it not just a collection, but a method that returns a collection. Method will not be executed on each iteration?
1 answer
No, the call to Foo will be made only once. Cycle
foreach (var i in Foo()) { // тело цикла } inside is replaced by this:
IEnumerable<T> x = Foo(); using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = enumerator.Current; // тело цикла } } That is, as we see, the method is called once.
(A detailed and more accurate explanation is in the details. )
The foreach construct is syntactic sugar. When compiling this construct:
foreach (var i in x) { // тело цикла } Replaced with approximately the following code:
using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = enumerator.Current; // тело цикла } } In this case, x is an instance of some object that contains the GetEnumerator() * method or implements the IEnumerable<T> (or IEnumerable ) interface and can be specified both as a variable and as an expression.
If in a loop the type of a variable differs from T :
IEnumerable<T> x = ...; foreach (SomeType i in x) { // тело цикла } That will be added to this type of cast. If unsuccessful, an exception will be thrown:
using (var enumerator = x.GetEnumerator()) { while (enumerator.MoveNext()) { var i = (SomeType)enumerator.Current; // тело цикла } } (But in the loop, you cannot change the loop variable i .)
The foreach construct also works for collections implementing the non- IEnumerable . In this case, the code is somewhat different, since the non-generic IEnumerator does not have the Dispose() method:
var enumerator = x.GetEnumerator(); while (enumerator.MoveNext()) { var i = enumerator.Current; // тело цикла } If the loop variable explicitly specifies the type ( foreach (SomeType i in Foo()) ), then the same type of casting is added:
var i = (SomeType)enumerator.Current; In this case, the type i will be SomeType , without specifying the type - object .
Additionally, I advise you to read about how iterators and IEnumerable / IEnumerable<T> . (For example, in the language specification, section 8.8.4.)
* In this case, the return type of this method must be an object that has the Current property open and a method with the signature bool MoveNext() . This is what is called "duck typing": you can not implement the IEnumerable<T> or IEnumerable interface, but simply provide the appropriate methods.
IEnumerableor stillIEnumerable<T>? - Grundy- one@andreycha, although the fundamental difference between them is only in the fact that in an
IEnumerable- i always an object, but in a generic version of a particular type? By the way, as I understand atforeach( A a in Foo())will be an attempt to cast an element toA, if the elements of the collection are of a different type - Grundy - one@andreycha: There is more subtlety for the case when
Fooreturns a non-genericIEnumerable, and the loop is written in the form offoreach (T x in Foo())- theenumerator.Currentcast toTis added. - VladD - 2Well, here's a heap for you: (1) the read-only loop variable, (2)
Foo()does not have to returnIEnumerableorIEnumerable<T>, there in the absence of an interface duck typing is enabled. - VladD - 2@andreycha by the way, foreach doesn't require IEnumerable / IEnumerator. In the same place, duck typing is enough to give him something that has a
GetEnumerator()method that returns something, which has theMoveNext()method and theCurrentproperty - PashaPash ♦