C#`foreach`行为 - 澄清?

时间:2015-07-10 14:05:29

标签: c# foreach

我已经阅读了Eric关于foreach枚举的文章here以及foreach可以使用的不同场景

为了防止旧的C#版本进行装箱,C#团队为foreach启用了鸭子类型,以便在非Ienumerable集合上运行。(公共GetEnumerator返回具有公共{{1}的内容}和MoveNext属性就足够了(。

所以,Eric写了sample

Current

但是我相信它有一些错别字(在class MyIntegers : IEnumerable { public class MyEnumerator : IEnumerator { private int index = 0; object IEnumerator.Current { return this.Current; } int Current { return index * index; } public bool MoveNext() { if (index > 10) return false; ++index; return true; } } public MyEnumerator GetEnumerator() { return new MyEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } 属性实现中缺少get访问者)这会阻止它编译(我已经通过电子邮件发送给他了)。

无论如何这是一个工作版本:

Current

确定。

根据MSDN

  

类型class MyIntegers : IEnumerable { public class MyEnumerator : IEnumerator { private int index = 0; public void Reset() { throw new NotImplementedException(); } object IEnumerator.Current { get { return this.Current; } } int Current { get { return index*index; } } public bool MoveNext() { if (index > 10) return false; ++index; return true; } } public MyEnumerator GetEnumerator() { return new MyEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } 如果实现了,则称为C   collection type接口实现了   System.Collections.IEnumerable通过满足以下条件的所有

  • C包含一个带有签名GetEnumerator()的公共实例方法,该签名返回struct-type,class-type或interface-type,在下文中称为E.

  • E包含一个带有签名MoveNext()和返回类型bool的公共实例方法。

  • E包含一个名为Current的公共实例属性,允许读取当前值。该属性的类型被称为集合类型的元素类型。

行。让我们将文档与Eric的样本

进行匹配

Eric的示例 被称为collection pattern,因为它确实实现了collection type接口(显然)。但是由于子弹3,不是(!) System.Collections.IEnumerablecollection pattern 公共实例属性名为Current。

MSDN说:

  

如果集合表达式是实现的类型   集合模式(如上所述),foreach的扩展   声明是:

MyEnumerator
  

否则,集合表达式是实现的类型    System.IEnumerable(!),foreach语句的扩展是:

E enumerator = (collection).GetEnumerator();
try {
   while (enumerator.MoveNext()) {
      ElementType element = (ElementType)enumerator.Current;
      statement;
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

问题#1

Eric的样本似乎都没有实现  IEnumerator enumerator = ((System.Collections.IEnumerable)(collection)).GetEnumerator(); try { while (enumerator.MoveNext()) { ElementType element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); } 也不collection pattern - 所以它不应该匹配上面指定的条件的任何。那么为什么我仍然可以通过以下方式迭代它:

System.IEnumerable

问题#2

为什么我必须提到 foreach (var element in (new MyIntegers() as IEnumerable )) { Console.WriteLine(element); } ?它已经是Ienumerable(!!),甚至在那之后,Isn编译器已经通过强制转换来完成这项任务:

new MyIntegers() as IEnumerable

就在这里:

((System.Collections.IEnumerable)(collection)).GetEnumerator() ?

那为什么它仍然要我提到不可数?

2 个答案:

答案 0 :(得分:8)

  

MyEnumerator没有所需的公共方法

是的,或者更确切地说,如果$ find bin/META-INF/ -type f bin/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat $ cat bin/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat lookup threadlookupog4j2.plugins.ThreadLookup threadLookup $ vi bin/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat ^A^Flookup^A^Lthreadlookup^[log4j2.plugins.ThreadLookup^LthreadLookup $ find logs -type f -name "thread-*" logs/thread-${threadLookup:threadName}.log logs/thread-${threadLookup:threadName}-1.log.gz</pre> 是公开的话。所需要的只是:

  • 公开的,可读的Current财产
  • 没有返回Current
  • 的类型参数的公共MoveNext()方法

这里缺少bool只是另一个错字,基本上。事实上,这个例子没有做到它的意思(防止拳击)。它正在使用public实施,因为您正在使用IEnumerable - 因此表达式类型为new MyIntegers() as IEnumerable,它只是在整个过程中使用该接口。

您声称它没有实现IEnumerable,(IEnumerable,btw),但它使用显式接口实现。

最简单的方法是在没有实施System.Collections.IEnumerable的情况下测试这类事情:

IEnumerable

现在,如果您将using System; class BizarreCollection { public Enumerator GetEnumerator() { return new Enumerator(); } public class Enumerator { private int index = 0; public bool MoveNext() { if (index == 10) { return false; } index++; return true; } public int Current { get { return index; } } } } class Test { static void Main(string[] args) { foreach (var item in new BizarreCollection()) { Console.WriteLine(item); } } } 设为私有,则无法编译。

答案 1 :(得分:4)

在MSDN上对System.IEnumerable的引用只不过是旧语言规范中的拼写错误,不存在这样的接口,我认为应该引用System.Collections.IEnumerable

您应该真正阅读您正在使用的C#版本的语言规范,C#5.0语言规范可用here

有关未能投射此示例的原因的一些进一步信息会导致错误,而不是回退到使用System.Collections.IEnumerable

foreach规范(第8.8.4节)中,您会看到规则略有变化(为简洁起见,已经删除了一些步骤):

  
      
  • 使用标识符GetEnumerator对类型X执行成员查找,而不使用类型参数。如果成员查找不产生匹配,或者产生歧义,或产生不是方法组的匹配,请检查可枚举接口,如下所述。如果成员查找产生除方法组或不匹配之外的任何内容,建议发出警告。
  •   
  • 使用生成的方法组和空参数列表执行重载解析。如果重载决策导致没有适用的方法,导致歧义,或导致单个最佳方法但该方法是静态的或不公开的,请检查可枚举的接口,如下所述。如果重载决策产生除明确的公共实例方法或没有适用的方法之外的任何内容,建议发出警告。
  •   
  • 如果GetEnumerator方法的返回类型E不是类,结构或接口类型,则会产生错误,并且不会采取进一步的步骤。
  •   
  • 在E上执行成员查找,标识符为Current,没有类型参数。如果成员查找没有匹配,则结果是错误,或者结果是除允许读取的公共实例属性之外的任何内容,产生错误并且不采取进一步的步骤。
  •   
  • 集合类型为X,枚举器类型为E,元素类型为Current属性的类型。
  •   

因此,从第一个项目符号点我们会发现public MyEnumerator GetEnumerator(),第二和第三个子弹没有错误。当我们到达第四个项目符号点时,没有名为Current的公共成员可用,这会导致您在没有强制转换的情况下看到错误,这种情况永远不会让编译器有机会查找可枚举的接口。

当您将实例明确转换为IEnumerable时,所有要求都会得到满足,因为类型IEnumerable和关联的IEnumerator满足所有要求。

同样来自文档:

  

以上步骤,如果成功,则明确地生成集合类型C,枚举器类型E和元素类型T.表单的foreach语句

foreach (V v in x) embedded-statement
     

然后扩展为:

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

因此,如果明确转发IEnumerable,您最终会得到以下内容:

  
      
  • C = System.Collections.IEnumerable
  •   
  • x = new MyIntegers() as System.Collections.IEnumerable
  •   
  • E = System.Collections.IEnumerator
  •   
  • T = System.Object
  •   
  • V = System.Object
  •   
{
    System.Collections.IEnumerator e = ((System.Collections.IEnumerable)(new MyIntegers() as System.Collections.IEnumerable)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            System.Object element = (System.Object)(System.Object)e.Current;
            Console.WriteLine(element);
        }
    }
    finally {
        System.IDisposable d = e as System.IDisposable;
        if (d != null) d.Dispose();
    }
}

这解释了为什么使用演员。