开发者

How does foreach call GetEnumerator()? Via IEnumerable reference or via...?

    static void Main(string[] args)
    {
        List<int> listArray = new List<int>();
        listArray.Add(100);
        foreach (int item in listArray)
            Console.WriteLine(item);
    }

a) When foreach statement calls listArray's IEnumerable<int>.GetEnumerator() implementation, does it call it via listArray.GetEnumerator() or IEnumerable<int>.GetEnumerator() or IEnumerable.GetEnumerator() ?

b) Similarly, when foreach references object returned by listArray's IEnumerable<int>.GetEnumerator(), does it reference this object via IEnumerator or IEnumerator<int> reference type?

thank you

EDIT:

Some of my questions will quote this text:

o Perform member lookup on the type X with identifier GetEnumerator and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.

o Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods.

o If the return type E of the GetEnumerator method is not a class, struct or interface type, an error is produced and no further steps are taken.

o Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.

o Member lookup is performed on E with the identifier MoveNext and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken.

o Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not bool, an error is produced and no further steps are taken.

o The collection type is X, the enumerator type is E, and the element type is the type of the Current property.

  • Otherwise, check for an enumerable interface: o If there is exactly one type T such that there is an implicit conversion from X to the interface System.Collections.Generic.IEnumerable, then the collection type is this interface, the enumerator type is the interface System.Collections.Generic.IEnumerator, and the element type is T.

  • Otherwise, if there is more than one such type T, then an error is produced and no further steps are taken.

  • Otherwise, if there is an implicit conversion from X to the System.Collections.IEnumerable interface, then the collection type is this interface, the enumerator type is the interface System.Collections.IEnumerator, and the element type is object.

  • Otherwise, an error is produced and no further steps are taken.

1)

Quote from Eric Lippert:

Option (1) is correct. Note that this means that the enumerator returned is an unboxed mutable struct.

The fact that this is a mutable struct has very real effects if you do something foolish like passing around the struct as though it were a reference type; it will be copied by value, not by reference.

From http://en.csharp-online.net/ECMA-334:_15.8.4_The_foreach_statement :

foreach (V v in x) embedded-statement

is then expanded to:

{
   E e = ((C)(x)).GetEnumerator();
   try {
      V v;
      while (e.MoveNext()) {
         v = (V)(T)e.Current;
         embedded-statement
      }
   }
   finally {
      … // Dispose e
   }
}

The variable e is not visible to or accessible to the expression x or the embedded statement or any other source code of the program.

In case开发者_StackOverflow of listArray, the returned enumerator is saved ( ie its value is saved ) in variable e ( thus variable e is a mutable struct ) .But according to the above excerpt, e is not accesible to my source code, so how would I be able to pass this struct around ( unless I write code that does manually what foreach statement does automatically )?

2)

Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.

It seems that if we implement GetEnumerator in the class ( X ) itself, then Current should also be implemented in the class ( E ) itself ( thus E shouldn't explicitly implement Current ), since compiler won't bother to check for IEnumerator<T> / IEnumerator interfaces in cases where member lookup ( on E with identifier Current ) doesn't produce a match?

3)

If there is exactly one type T such that there is an implicit conversion from X to the interface System.Collections.Generic.IEnumerable, then the collection type is this interface, the enumerator type is the interface System.Collections.Generic.IEnumerator, and the element type is T.

According to the above, if foreach has to check for IEnumerable<T> interface, then foreach will always use IEnumerator<T> version of Current? Thus, if E explicitly implements IEnumerator<T> version of Current and if it also implements another version of Current in the class itself, the foreach will always call IEnumerable<T> version of Current?

4)

The GetEnumerator method is documented as returning one of these:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

What do you mean by one of these ( as in plural )? Link you provided says GetEnumerator ( as implemented by List<T> ) only returns struct type.

5)

g. The collection type is X, the enumerator type is E, and the element type is the type of the Current property

Perhaps a useless question - according to above, foreach doesn't check what type of elements some user-defined collection actually stores, but instead assumes that the type of elements is the same as the type returned by Current property?


(a) When foreach statement calls listArray's IEnumerable.GetEnumerator() implementation, does it call it via (1) listArray.GetEnumerator() or (2) IEnumerable.GetEnumerator() or (3) IEnumerable.GetEnumerator() ?

Option (1) is correct. Note that this means that the enumerator returned is an unboxed mutable struct. The GetEnumerator method is documented as returning one of these:

http://msdn.microsoft.com/en-us/library/x854yt9s.aspx

The fact that this is a mutable struct has very real effects if you do something foolish like passing around the struct as though it were a reference type; it will be copied by value, not by reference.

(1) But according to the above excerpt, e is not accesible to my source code, so how would I be able to pass this struct around ( unless I write code that does manually what foreach statement does automatically )?

You are correct. I was not clear. My point was that if you write code that does what foreach does and you mess with the enumerator object yourself then you have to be careful. The CLR team realized that the vast majority of people would be using the foreach loop and would thereby not be exposed to the hazard of accidentally using the enumerator incorrectly.

(2) It seems that if we implement GetEnumerator in the class X itself, then Current should also be implemented in the class E itself since the compiler won't bother to check for the explicit interface members in cases where member lookup doesn't produce a match?

Correct.

(3) if foreach has to check for IEnumerable<T> interface, then foreach will always use IEnumerator<T> version of Current? Thus, if E explicitly implements IEnumerator<T> version of Current and if it also implements another version of Current in the class itself, the foreach will always call IEnumerable<T> version of Current?

Correct. If you get to the point where we're looking on the interface then we're going to use the interface.

(4) What do you mean by "one of these"

I meant that it will return an instance of the struct.

(5) according to above, foreach doesn't check what type of elements some user-defined collection actually stores, but instead assumes that the type of elements is the same as the type returned by Current property?

Correct. It does check that the cast succeeds. For example, if you say

foreach(int x in myObjects)

where myObjects gives you an enumerator whose Current is of type object, then the loop assumes that each object can be successfully cast to int, and throws an exception at runtime if that is incorrect. But if you say similarly:

foreach(string x in myInts)

then the compiler will note that if Current returns an int then the collection never contains a string, and will fail to compile the program.

(b) Similarly, when foreach references object returned by listArray's IEnumerable.GetEnumerator(), does it reference this object via IEnumerator or IEnumerator reference type?

The question is predicated upon the answer to the first question being option (2). Since the question is predicated upon a falsehood, it cannot be answered sensibly.


The behavior of foreach is spelled out in the language specification, section 8.8.4. In a nutshell

foreach (T t in expression)

  • If expression is an array*, use IEnumerable interface (*see Eric Lippert's comment below.)
  • Else if expression has GetEnumerator method, use that
  • Else if expression is convertible to IEnumerable<T>, use that interface and IEnumerator<T> (and associated methods)
  • Else if expression is convertible to IEnumerable, use that interface and IEnumerator (and associated methods)

And there are various error conditions and things I'm glossing over. But, in short, if your collection is generic, it will go for the generic interface options.


From the C# 3.0 language spec (Sec. 8.8.4):

The compile-time processing of a foreach statement first determines the collection type, enumerator type and element type of the expression. This determination proceeds as follows:

  1. If the type X of expression is an array type then there is an implicit reference conversion from X to the System.Collections.IEnumerable interface (since System.Array implements this interface). The collection type is the System.Collections.IEnumerable interface, the enumerator type is the System.Collections.IEnumerator interface and the element type is the element type of the array type X.
  2. Otherwise, determine whether the type X has an appropriate GetEnumerator method:

    a. Perform member lookup on the type X with identifier GetEnumerator and no type arguments. If the member lookup does not produce a match, or it produces an ambiguity, or produces a match that is not a method group, check for an enumerable interface as described below. It is recommended that a warning be issued if member lookup produces anything except a method group or no match.

    b. Perform overload resolution using the resulting method group and an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, check for an enumerable interface as described below. It is recommended that a warning be issued if overload resolution produces anything except an unambiguous public instance method or no applicable methods.

    c. If the return type E of the GetEnumerator method is not a class, struct or interface type, an error is produced and no further steps are taken.

    d. Member lookup is performed on E with the identifier Current and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a public instance property that permits reading, an error is produced and no further steps are taken.

    e. Member lookup is performed on E with the identifier MoveNext and no type arguments. If the member lookup produces no match, the result is an error, or the result is anything except a method group, an error is produced and no further steps are taken.

    f. Overload resolution is performed on the method group with an empty argument list. If overload resolution results in no applicable methods, results in an ambiguity, or results in a single best method but that method is either static or not public, or its return type is not bool, an error is produced and no further steps are taken.

    g. The collection type is X, the enumerator type is E, and the element type is the type of the Current property.

In summary, the compiler acts as if the foreach were the following code, making polymorphic calls and looking at the defined enumerable interface definition (if any) to determine the proper types and methods:

var iterator = listArray.GetEnumerator();
while(iterator.MoveNext())
{
   var item = iterator.Current;
   Console.WriteLine(item);
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜