22

foreach在 C# 中究竟是如何实现的?

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

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

但是我不确定到底发生了什么。enumerator.Current每个周期使用什么方法返回?它是返回 [for each cycle] 还是需要一个匿名函数或其他东西来执行它的主体foreach

4

2 回答 2

30

它不使用匿名函数,不。基本上,编译器将代码转换为与您在此处显示的 while 循环大致等效的内容。

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

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

  • 迭代数组时(在编译时已知),编译器可以使用循环计数器并与数组的长度进行比较,而不是使用IEnumerator
  • foreach将在最后处理迭代器;这很简单IEnumerator<T>which extends IDisposable,但IEnumerator 不是,编译器插入一个检查以在执行时测试迭代器是否实现IDisposable
  • 您可以迭代不实现IEnumerableor的类型IEnumerable<T>,只要您有一个适用的GetEnumerator()方法,该方法返回具有合适的CurrentandMoveNext()成员的类型。如注释中所述,类型也可以实现IEnumerableIEnumerable<T>显式,但具有返回/GetEnumerator()以外的类型的公共方法。参见示例 - 这避免了在许多情况下不必要地创建引用类型对象。IEnumeratorIEnumerator<T>List<T>.GetEnumerator()

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

于 2012-06-24T16:32:55.173 回答
10

惊讶于没有触及确切的实现。虽然您在问题中发布的内容是最简单的形式,但完整的实现(包括枚举器处理、强制转换等)位于规范的 8.8.4 部分。

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

  1. 如果该类型有一个名为 public/non-static/non-generic/parameterless 的方法GetEnumerator,它返回具有公共MoveNext方法和公共Current属性的东西。 正如 Eric Lippert 先生在这篇博客文章中所指出的,这是为了适应前通用时代的类型安全和在值类型的情况下与装箱相关的性能问题。请注意,这是鸭子打字的情况。例如这有效:

    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
    {
    
    }
    

    然后编译器将其翻译为:

    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();
    }
    
  2. 如果该类型实现IEnumerable了 whereGetEnumerator返回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方法):

    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
    }
    

需要注意的一些有趣的事情是:

  1. 在上述两种情况下,Enumerator类都应该有一个公共MoveNext方法和一个公共Current属性。换句话说,如果你正在实现IEnumerator接口,它必须被隐式实现。例如,foreach不适用于此枚举器:

    public class MyEnumerator : IEnumerator
    {
        void IEnumerator.Reset()
        {
            throw new NotImplementedException();
        }
    
        object IEnumerator.Current
        {
            get { throw new NotImplementedException(); }
        }
    
        bool IEnumerator.MoveNext()
        {
            throw new NotImplementedException();
        }
    }
    

    (感谢 Roy Namir 指出这一点。foreach实现并不像表面上看起来那么容易)

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

    class Test : IEnumerable<int>
    {
        public SomethingEnumerator GetEnumerator()
        {
            //this one is called
        }
    
        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
    
        }
    }
    

    如果您没有公共实现(即只有显式实现),则优先级类似于IEnumerator<T>> IEnumerator

  3. foreach在集合元素被转换回类型(在foreach循环本身中指定)的实现中涉及一个转换运算符。这意味着即使你写了SomethingEnumerator这样的:

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

    你可以写:

    foreach (Something thing in new Test())
    {
    
    }
    

    因为Something是类型兼容object,遵循 C# 规则,或者换句话说,如果两种类型之间可能存在显式转换,编译器就会允许它。否则编译器会阻止它。实际的转换是在运行时执行的,可能会也可能不会失败。

于 2013-12-01T10:56:19.883 回答