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?

  • 3
    why not just check? and no, the function is called once - Grundy
  • Good question) - Sergey
  • I am trying to make a move with entering when debugging, but I don’t throw a method - Sergey
  • @Grundy: Make the answer from this. - VladD
  • @VladD, Andrey has already painted well :-) - Grundy

1 answer 1

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 still IEnumerable<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 at foreach( A a in Foo()) will be an attempt to cast an element to A , 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-generic IEnumerable , and the loop is written in the form of foreach (T x in Foo()) - the enumerator.Current cast to T is added. - VladD
  • 2
    Well, here's a heap for you: (1) the read-only loop variable, (2) Foo() does not have to return IEnumerable or IEnumerable<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 the MoveNext() method and the Current property - PashaPash