如何在C#中实现foreach?

时间:2012-06-24 16:30:53

标签: c# foreach language-implementation language-specifications

在C#中实现foreach的确切程度如何?

我想它的一部分看起来像:

var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
  // do some stuff here
}

然而,我不确定究竟发生了什么。每个周期使用什么方法返回enumerator.Current?它是[返回] [每个周期]还是采用匿名函数或其他东西来执行foreach的主体?

2 个答案:

答案 0 :(得分:28)

它不使用匿名函数,不。基本上,编译器将代码转换为大致相当于的东西到你在这里显示的while循环。

foreach 不是函数调用 - 它内置于语言本身,就像for循环和while循环一样。它不需要返回任何东西或“接受”任何类型的功能。

请注意foreach有一些有趣的皱纹:

  • 当迭代数组(在编译时已知)时,编译器可以使用循环计数器并与数组的长度进行比较,而不是使用IEnumerator
  • foreach将在最后处置迭代器;这对于扩展IEnumerator<T>的{​​{1}}来说很简单,但由于IDisposable ,编译器会在执行时插入检查来测试迭代器是否实现{{ 1}}
  • 您可以迭代未实现IEnumeratorIDisposable的类型,只要您有适用的IEnumerable方法,该方法返回的类型具有合适的IEnumerable<T>GetEnumerator()个成员。如评论中所述,类型可以 明确地实现CurrentMoveNext(),但具有公共IEnumerable方法,该方法返回{{1}以外的类型} / IEnumerable<T>。有关示例,请参阅List<T>.GetEnumerator() - 这可以避免在许多情况下不必要地创建引用类型对象。

有关详细信息,请参阅C#4规范的第8.8.4节。

答案 1 :(得分:10)

感到惊讶的是没有触及确切的实现。虽然您在问题中发布的内容是最简单的形式,但完整的实现(包括枚举器处理,转换等)位于8.8.4 section of the spec.

现在有两种情况可以在类型上运行foreach循环:

  1. 如果类型具有名为GetEnumerator的公共/非静态/非通用/无参数方法,该方法返回具有公共MoveNext方法和公共{{ 1}} property。 As noted by Mr Eric Lippert in this blog article,这是为了在价值类型的情况下适应类型安全和拳击相关性能问题的预通用时代。请注意,这是 duck typing 的情况。例如,这有效:

    Current

    然后由编译器将其翻译为:

    class Test
    {
        public SomethingEnumerator GetEnumerator()
        {
    
        }
    }
    
    class SomethingEnumerator
    {
        public Something Current //could return anything
        {
            get { return ... }
        }
    
        public bool MoveNext()
        {
    
        }
    }
    
    //now you can call
    foreach (Something thing in new Test()) //type safe
    {
    
    }
    
  2. 如果类型实现E enumerator = (collection).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); } IEnumerable返回GetEnumerator,其中包含公共IEnumerator方法和公共MoveNext属性但一个有趣的子案例是,即使您明确实施Current(即IEnumerable类上没有公共GetEnumerator方法),您也可以拥有Test

    foreach

    这是因为在这种情况下class Test : IEnumerable { IEnumerator IEnumerable.GetEnumerator() { } } 被实现为(假设类中没有其他公共foreach方法):

    GetEnumerator

    如果类型显式实现IEnumerator enumerator = ((IEnumerable)(collection)).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; statement; } } finally { IDisposable disposable = enumerator as System.IDisposable; if (disposable != null) disposable.Dispose(); } ,则IEnumerable<T>将转换为(假设类中没有其他公共foreach方法):

    GetEnumerator

  3. 很少有值得注意的事情是:

    1. 在上述两种情况下,IEnumerator<T> enumerator = ((IEnumerable<T>)(collection)).GetEnumerator(); try { ElementType element; //pre C# 5 while (enumerator.MoveNext()) { ElementType element; //post C# 5 element = (ElementType)enumerator.Current; //Current is `T` which is cast statement; } } finally { enumerator.Dispose(); //Enumerator<T> implements IDisposable } 类都应具有公共Enumerator方法和公共MoveNext属性。换句话说,如果您正在实现Current接口,则必须隐式实现。例如,IEnumerator不适用于此枚举器:

      foreach

      (感谢Roy Namir指出这一点。public class MyEnumerator : IEnumerator { void IEnumerator.Reset() { throw new NotImplementedException(); } object IEnumerator.Current { get { throw new NotImplementedException(); } } bool IEnumerator.MoveNext() { throw new NotImplementedException(); } } 实施并不像表面上看起来那么简单。

    2. 枚举器优先级 - 就像你有一个foreach方法一样,那么这是public GetEnumerator的默认选择,无论是谁实现它。例如:

      foreach

      如果您没有公开实现(即只有显式实现),那么优先级就像class Test : IEnumerable<int> { public SomethingEnumerator GetEnumerator() { //this one is called } IEnumerator<int> IEnumerable<int>.GetEnumerator() { } } &gt; IEnumerator<T>

    3. 有一个转换操作符参与IEnumerator的实现,其中collection元素被强制转换为类型(在foreach循环本身中指定)。这意味着即使您已经像这样写了foreach

      SomethingEnumerator

      你可以写:

      class SomethingEnumerator
      {
          public object Current //returns object this time
          {
              get { return ... }
          }
      
          public bool MoveNext()
          {
      
          }
      }
      

      因为foreach (Something thing in new Test()) { } Something类型兼容,所以使用C#规则,或者换句话说,如果两种类型之间存在显式转换,编译器会允许它。否则编译器会阻止它。实际演员表是在运行时执行的,可能会也可能不会失败。