foreach如何调用GetEnumerator()?通过IEnumerable参考或通过...?

use*_*291 21 c#

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

a)在foreach声明中呼吁listArray's IEnumerable<int>.GetEnumerator()实现,它通过调用它listArray.GetEnumerator()IEnumerable<int>.GetEnumerator()IEnumerable.GetEnumerator()

b)同样,当foreach引用对象返回时listArray's IEnumerable<int>.GetEnumerator(),它是通过引用IEnumerator还是IEnumerator<int>引用类型引用该对象?

谢谢

编辑:

我的一些问题将引用此文:

o使用标识符GetEnumerator对类型X执行成员查找,而不使用类型参数.如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述.如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告.

o使用生成的方法组和空参数列表执行重载解析.如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,请检查可枚举的接口,如下所述.如果重载决策产生除明确的公共实例方法或没有适用的方法之外的任何内容,建议发出警告.

o如果GetEnumerator方法的返回类型E不是类,结构或接口类型,则会产生错误,并且不会采取进一步的步骤.

o在E上执行成员查找,标识符为Current,没有类型参数.如果成员查找不产生匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不执行进一步的步骤.

o成员查找在E上执行,标识符为MoveNext,没有类型参数.如果成员查找不产生匹配,则结果是错误,或者结果是除方法组之外的任何内容,产生错误并且不执行进一步的步骤.

o使用空参数列表对方法组执行重载分辨率.如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或非公共的,或者其返回类型不是bool,则产生错误并且不采取进一步的步骤.

o集合类型为X,枚举器类型为E,元素类型为Current属性的类型.

  • 否则,检查一个可枚举的接口:o如果只有一个类型T,从而存在从X到接口System.Collections.Generic.IEnumerable的隐式转换,那么集合类型就是这个接口,枚举器类型就是接口System.Collections.Generic.IEnumerator,元素类型为T.

  • 否则,如果存在多个这样的类型T,则产生错误并且不采取进一步的步骤.

  • 否则,如果存在从X到System.Collections.IEnumerable接口的隐式转换,则集合类型为此接口,枚举器类型为接口System.Collections.IEnumerator,元素类型为object.

  • 否则,将产生错误,并且不会采取进一步的步骤.

1)

引自Eric Lippert:

选项(1)是正确的.请注意,这意味着返回的枚举器是一个未装箱的可变结构.

事实上,这是一个可变的结构,如果你做一些愚蠢的事情,比如传递结构,就像它是一个引用类型一样,它具有非常实际的效果; 它将按值复制,而不是通过引用复制.

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

foreach(V v in x)嵌入式语句

然后扩展到:

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

变量e对表达式x或嵌入语句或程序的任何其他源代码不可见或不可访问.

在这种情况下listArray,返回的枚举器被保存(即它的值被保存)在变量中e(因此变量e是一个可变的结构).但根据上面的摘录,e我的源代码是不可访问的,所以我怎么能够通过这个结构(除非我编写代码手动执行什么foreach语句自动执行)?

2)

成员查找在E上执行,标识符为Current,没有类型参数.如果成员查找不产生匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不执行进一步的步骤.

似乎如果我们GetEnumerator在class(X)本身中实现,那么Current也应该在class(E)本身中实现(因此E不应该显式实现Current),因为编译器IEnumerator<T> / IEnumerator在成员查找的情况下不会检查接口(on E带标识符Current)不会产生匹配?

3)

如果只有一个类型T,从而存在从X到接口System.Collections.Generic.IEnumerable的隐式转换,则集合类型是此接口,枚举器类型是接口System.Collections.Generic.IEnumerator,并且元素类型是T.

根据以上所述,如果foreach要检查IEnumerable<T>接口,那么foreach将始终使用的IEnumerator<T>版本Current?因此,如果E显式实现了IEnumerator<T>版本,Current并且如果它还Current在类本身中实现了另一个版本,那么foreach它将始终调用IEnumerable<T>版本的Current

4)

GetEnumerator方法记录为返回以下其中一个:

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

你是其中之一(复数形式)是什么意思?您提供的链接说GetEnumerator(由实现List<T>)仅返回struct类型.

5)

G.集合类型为X,枚举器类型为E,元素类型为Current属性的类型

也许一个无用的问题 - 根据上面的说法,foreach不会检查某些用户定义的集合实际存储的元素类型,而是假设元素的类型与Current属性返回的类型相同?

Eri*_*ert 16

(a)当foreach语句调用listArray的IEnumerable.GetEnumerator()实现时,是否通过(1)listArray.GetEnumerator()或(2)IEnumerable.GetEnumerator()或(3)IEnumerable.GetEnumerator()调用它?

选项(1)是正确的.请注意,这意味着返回的枚举器是一个未装箱的可变结构.GetEnumerator方法记录为返回以下其中一个:

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

事实上,这是一个可变的结构,如果你做一些愚蠢的事情,比如传递结构,就像它是一个引用类型一样,它具有非常实际的效果; 它将按值复制,而不是通过引用复制.

(1)但是根据上面的摘录,e不能访问我的源代码,那么我怎么能够传递这个结构(除非我编写的代码手动执行foreach语句自动执行的操作)?

你是对的.我不清楚.我的观点是,如果你编写代码来执行foreach所做的事情并且你自己弄乱了枚举器对象,那么你必须要小心.CLR团队意识到绝大多数人将使用foreach循环,因此不会暴露于不正确地使用枚举器的危险.

(2)似乎如果我们在类X本身中实现GetEnumerator,那么Current也应该在类E本身中实现,因为在成员查找不产生的情况下编译器不会费心检查显式接口成员比赛?

正确.

(3)如果foreach必须检查IEnumerable<T>接口,那么foreach将始终使用IEnumerator<T>Current的版本?因此,如果E明确地实现IEnumerator<T>了Current的版本,并且它还在类本身中实现了另一个版本的Current,那么foreach将始终调用IEnumerable<T>Current的版本?

正确.如果你到达我们正在查看界面的点,那么我们将使用该界面.

(4)"其中一个"是什么意思

我的意思是它将返回结构的一个实例.

(5)根据上面的说法,foreach不检查某些用户定义的集合实际存储的元素类型,而是假设元素的类型与Current属性返回的类型相同?

正确.它会检查演员是否成功.例如,如果你说

foreach(int x in myObjects)
Run Code Online (Sandbox Code Playgroud)

其中myObjects为您提供了Current类型为object的枚举器,然后循环假定每个对象都可以成功转换为int,并在运行时抛出异常(如果不正确).但如果你说同样的话:

foreach(string x in myInts)
Run Code Online (Sandbox Code Playgroud)

然后编译器会注意到,如果Current返回一个int,那么该集合永远不会包含一个字符串,并且将无法编译该程序.

(b)类似地,当foreach引用listArray的IEnumerable.GetEnumerator()返回的对象时,它是否通过IEnumerator或IEnumerator引用类型引用此对象?

问题取决于第一个问题的答案是选项(2).由于这个问题是以虚假为基础的,因此无法合理地回答.


Ant*_*ram 14

foreach的行为在语言规范第8.8.4节中详细说明.简而言之

foreach(表达式中的T t)

  • 如果expression是一个数组*,请使用IEnumerable接口(*请参阅下面的Eric Lippert的评论.)
  • 否则,如果表达式具有GetEnumerator方法,请使用它
  • 如果表达式可转换为IEnumerable<T>,则使用该接口和IEnumerator<T>(以及相关方法)
  • 如果表达式可转换为IEnumerable,则使用该接口和IEnumerator(以及相关方法)

并且存在各种错误条件和我正在掩饰的事情.但是,简而言之,如果您的集合是通用的,那么它将用于通用接口选项.

  • 请注意,第一个要点可能具有误导性.在使用foreach枚举数组(或字符串!)时,C#编译器生成的代码实际上不*使用IE <T>接口.相反,它生成与您通常使用的相同的"for(int i = 0; i <arr.Length; ++ i)"循环.为了类型分析*,编译器将数组视为IE <T>*,但实际上并不*以这种方式生成代码*. (12认同)

Kei*_*thS 8

从C#3.0语言规范(Sec.8.8.4):

foreach语句的编译时处理首先确定表达式的集合类型,枚举器类型和元素类型.该决定如下:

  1. 如果表达式的X类型是数组类型,则存在从X到System.Collections.IEnumerable接口的隐式引用转换(因为System.Array实现了此接口).集合类型是System.Collections.IEnumerable接口,枚举器类型是System.Collections.IEnumerator接口,元素类型是数组类型X的元素类型.
  2. 否则,确定类型X是否具有适当的GetEnumerator方法:

    一个.使用标识符GetEnumerator对类型X执行成员查找,而不使用类型参数.如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述.如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告.

    湾 使用生成的方法组和空参数列表执行重载解析.如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,请检查可枚举的接口,如下所述.如果重载决策产生除明确的公共实例方法或没有适用的方法之外的任何内容,建议发出警告.

    C.如果GetEnumerator方法的返回类型E不是类,结构或接口类型,则会产生错误,并且不会采取进一步的步骤.

    d.成员查找在E上执行,标识符为Current,没有类型参数.如果成员查找不产生匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不执行进一步的步骤.

    即 成员查找在E上执行,标识符为MoveNext,没有类型参数.如果成员查找不产生匹配,则结果是错误,或者结果是除方法组之外的任何内容,产生错误并且不执行进一步的步骤.

    F.使用空参数列表在方法组上执行重载分辨率.如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或非公共的,或者其返回类型不是bool,则产生错误并且不采取进一步的步骤.

    G.集合类型为X,枚举器类型为E,元素类型为Current属性的类型.

总之,编译器就像foreach是以下代码一样,进行多态调用并查看定义的可枚举接口定义(如果有)以确定正确的类型和方法:

var iterator = listArray.GetEnumerator();
while(iterator.MoveNext())
{
   var item = iterator.Current;
   Console.WriteLine(item);
}
Run Code Online (Sandbox Code Playgroud)

  • 当然还有处理枚举器的代码.另请注意,如果集合是数组或字符串,则不是生成的代码. (3认同)