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.
IEnumerable
or 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
Foo
returns a non-genericIEnumerable
, and the loop is written in the form offoreach (T x in Foo())
- theenumerator.Current
cast toT
is added. - VladD - 2Well, here's a heap for you: (1) the read-only loop variable, (2)
Foo()
does not have to returnIEnumerable
orIEnumerable<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 theCurrent
property - PashaPash ♦