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

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

a)当foreach语句调用listArray's IEnumerable.GetEnumerator()实现时,是否通过listArray.GetEnumerator()IEnumerable.GetEnumerator()IEnumerable.GetEnumerator()调用它?

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

谢谢

编辑:

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

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 } } 

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

listArray情况下,返回的枚举器被保存(即它的值被保存)在变量e (因此变量e是一个可变的结构)。但根据上面的摘录, e不能访问我的源代码,那么我该如何能够传递这个结构(除非我编写的代码手动执行foreach语句自动执行的操作)?

2)

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

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

3)

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

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

4)

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

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

你是其中之一 (复数forms)是什么意思? 你提供的链接说GetEnumerator (由List )只返回struct类型。

5)

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

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

(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接口,那么foreach将始终使用IEnumerator版本的Current? 因此,如果E显式实现了Current的IEnumerator版本,并且它还在类本身中实现了另一个版本的Current,那么foreach将始终调用IEnumerable版本的Current?

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

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

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

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

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

 foreach(int x in myObjects) 

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

 foreach(string x in myInts) 

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

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

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

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

foreach(表达式中的T t)

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

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

从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); }