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

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

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使用标识符MoveNext在E上执行成员查找,并且没有类型参数。 如果成员查找不匹配,则结果为错误,或者结果为除方法组以外的任何结果,则会产生错误,并且不会采取进一步的步骤。

o重载解析在具有空参数列表的方法组上执行。 如果重载解析导致没有适用的方法,导致模糊不清,或者导致一个最好的方法,但该方法是静态的或不公开的,或者它的返回类型不是bool,则会产生错误并且不会采取进一步的步骤。

o集合类型是X,枚举类型是E,元素类型是当前属性的类型。

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

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

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

  • 否则,会产生错误,并且不会采取进一步的措施。

  • 1)

    来自Eric Lippert的报价:

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

    事实上,这是一个可变的结构,如果你做了一些愚蠢的事情,就像是一个引用类型一样, 它将被值复制,而不是通过引用。

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

    foreach(V x in)嵌入语句

    然后扩展为:

    {
       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<T> / IEnumerator接口在成员查找(在标识符为Current E上)不会产生匹配的情况下?

    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,元素类型是当前属性的类型

    也许一个无用的问题 - 根据上述, 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<T>接口,那么foreach将始终使用IEnumerator<T>版本的Current? 因此,如果E明确实现了IEnumerator<T>版本的Current,并且如果它也在类本身中实现了另一个版本的Current,则foreach将始终调用IEnumerable<T>版本的Current?

    正确。 如果你到了我们在界面上看的地方,那么我们将使用界面。

    (4)你的意思是“其中之一”

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

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

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

    foreach(int x in myObjects)
    

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

    foreach(string x in myInts)
    

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

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

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


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

    foreach(表达式中的T t)

  • 如果表达式是数组*,则使用IEnumerable接口(*请参阅下面的Eric Lippert的评论。)
  • 否则,如果表达式具有GetEnumerator方法,请使用该方法
  • 否则,如果表达式可转换为IEnumerable<T> ,则使用该接口和IEnumerator<T> (及相关方法)
  • 否则,如果表达式可转换为IEnumerable ,则使用该接口和IEnumerator (及相关方法)
  • 有各种各样的错误条件和我正在掩饰的事情。 但是,简而言之,如果你的集合是通用的,它将用于通用接口选项。


    从C#3.0语言规范(第8.8.4节):

    foreach语句的编译时处理首先确定表达式的集合类型,枚举类型和元素类型。 该确定过程如下进行:

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

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

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

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

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

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

    F。 重载解析在具有空参数列表的方法组上执行。 如果重载解析导致没有适用的方法,导致模糊不清,或者导致一个最好的方法,但该方法是静态的或不公开的,或者它的返回类型不是bool,则会产生错误并且不会采取进一步的步骤。

    G。 集合类型是X,枚举类型是E,元素类型是当前属性的类型。

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

    var iterator = listArray.GetEnumerator();
    while(iterator.MoveNext())
    {
       var item = iterator.Current;
       Console.WriteLine(item);
    }
    
    链接地址: http://www.djcxy.com/p/53929.html

    上一篇: How does foreach call GetEnumerator()? Via IEnumerable reference or via...?

    下一篇: Protect ArrayList from write access