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

时间:2010-09-09 19:16:39

标签: c#

3 个答案:

答案 0 :(得分:15)

  

(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明确地实现了Current的IEnumerator<T>版本,并且它还在类本身中实现了另一个版本的Current,那么foreach将始终调用Current的IEnumerable<T>版本?

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

  

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

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

  

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

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

foreach(int x in myObjects)

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

foreach(string x in myInts)

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

  

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

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

答案 1 :(得分:14)

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

foreach(表达式中的T t)

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

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

答案 2 :(得分: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执行成员查找,而不使用类型参数。如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述。如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告。

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

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

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

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

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

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

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

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