C#'foreach'行为—;澄清

C#'foreach'行为—;澄清,c#,foreach,C#,Foreach,我读过Eric关于foreach枚举和foreach可以工作的不同场景的文章 为了防止旧的C#版本进行装箱,C#团队启用了foreach的duck类型,以便在非Ienumerable集合上运行。(一个公共GetEnumerator返回具有publicMoveNext和Current属性的内容就足够了() 埃里克写了一篇: 但我相信它有一些拼写错误(在当前的属性实现中缺少get accessor),这妨碍了它的编译(我已经给他发了电子邮件) 无论如何,这里有一个工作版本: class MyInte

我读过Eric关于foreach枚举和foreach可以工作的不同场景的文章

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

埃里克写了一篇:

但我相信它有一些拼写错误(在当前的属性实现中缺少get accessor),这妨碍了它的编译(我已经给他发了电子邮件)

无论如何,这里有一个工作版本:

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
实现了
System.Collections.IEnumerable
接口实现
收集模式
满足以下所有标准:

  • C包含一个带有签名GetEnumerator()的公共实例方法,该方法返回结构类型、类类型或接口类型,在下面的文本中称为E

  • E包含一个公共实例方法,其签名为MoveNext(),返回类型为bool

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

好的,让我们把这些文件和Eric的样本匹配起来

Eric的示例被称为
集合类型
,因为它确实实现了
System.Collections.IEnumerable
接口(虽然是显式的)。但它不是(!)集合模式,因为项目符号3:
MyEnumerator
没有名为Current的公共实例属性

说:

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

否则,集合表达式属于实现 System.IEnumerable(!),foreach语句的扩展为:

问题1

Eric的示例似乎既没有实现
收集模式
系统.IEnumerable
-因此它不应该与上面指定的任何条件匹配。那么我为什么仍然可以通过以下方式迭代它:

 foreach (var element in (new MyIntegers() as IEnumerable ))
             {
                 Console.WriteLine(element);
             }
问题2

为什么我必须将新的MyIntegers()称为IEnumerable?它已经是IEnumerable(!!)了,即使在这之后,编译器不是已经通过强制转换自己完成了这项工作了吗

((System.Collections.IEnumerable)(collection)).GetEnumerator() ?
就在这里:

 IEnumerator enumerator = 
            ((System.Collections.IEnumerable)(collection)).GetEnumerator();
    try {
       while (enumerator.MoveNext()) {
       ...
那为什么它还想让我说“我是数不清的”

MyEnumerator没有必需的公共方法

是的,它是公开的,或者更确切地说,如果
Current
是公开的,它就会公开。所需要的只是它有:

  • 公共、可读的
    当前属性
  • 没有返回类型参数的公共
    MoveNext()
    方法
    bool
这里缺少
public
基本上只是另一个输入错误。事实上,这个示例并没有达到它的目的(防止装箱)。它使用
IEnumerable
实现,因为您使用的是
new MyIntegers()由于IEnumerable
,因此表达式类型为
IEnumerable
,并且它始终使用接口

您声称它没有实现
IEnumerable
,(即
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);
        }
    }
}

现在,如果将
设置为当前的
私有,它将不会编译。

MSDN上对
System.IEnumerable
的引用只不过是旧语言规范中的一个输入错误,不存在这样的接口,我相信它应该是指
System.Collections.IEnumerable

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

有关为什么未能强制转换此示例会导致错误而不是返回到使用
System.Collections.IEnumerable
的原因的更多信息:

foreach
(第8.8.4节)的规范中,您将看到规则有了细微的变化(为了简洁起见,一些步骤被删减):

  • 对标识符为GetEnumerator且无类型参数的类型X执行成员查找。如果成员查找未生成匹配项,或产生歧义,或生成的匹配项不是方法组,请检查可枚举接口,如下所述。如果成员查找生成任何内容,建议发出警告除了方法组或没有匹配项
  • 使用生成的方法组和空参数列表执行重载解析。如果重载解析导致没有适用的方法、导致歧义或导致单个最佳方法,但该方法是静态的或非公共的,请检查可枚举接口,如下所述。建议在如果重载解析生成除明确的公共实例方法或无适用方法之外的任何内容,则为ssued
  • 如果GetEnumerator方法的返回类型E不是类、结构或接口类型,则会产生错误,并且不会采取进一步的步骤
  • 在标识符为当前且无类型参数的E上执行成员查找。如果成员查找未生成匹配项,则结果为错误,或者结果为除允许读取的公共实例属性以外的任何内容,则会生成错误,并且不会采取进一步的步骤
  • 集合类型为X,枚举数为ty
    ((System.Collections.IEnumerable)(collection)).GetEnumerator() ?
    
     IEnumerator enumerator = 
                ((System.Collections.IEnumerable)(collection)).GetEnumerator();
        try {
           while (enumerator.MoveNext()) {
           ...
    
    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);
            }
        }
    }
    
    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
        }
    }
    
    {
        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();
        }
    }