如何在C#中实现foreach?

Jam*_*xon 22 c# foreach language-implementation language-specifications

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

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

var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
  // do some stuff here
}
Run Code Online (Sandbox Code Playgroud)

但是我不确定究竟发生了什么.enumerator.Current每个周期使用什么方法返回?它是为[每个循环]返回还是需要匿名函数或其他东西才能执行foreach

Jon*_*eet 30

它不使用匿名函数,不.基本上,编译器将代码转换为与此处显示的while循环大致相同的代码.

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

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

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

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

  • 你的第三个项目不只是针对那些没有实现`IEnumerable`的类型:当一个类实现它时,它还提供一个与`IEnumerable <T> .GetEnumerator`不同的`GetEnumerator`方法,该类型自己的`GetEnumerator`将会被使用.标准类`List <T>`就是一个很好的例子. (4认同)

naw*_*fal 11

惊讶于没有触及确切的实施.虽然您在问题中发布的内容是最简单的形式,但完整的实现(包括枚举器处理,转换等)在规范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
    {
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    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();
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果类型实现IEnumerable,其中GetEnumerator的回报IEnumerator有一个公共MoveNext的方法和公共Current财产.但是一个有趣的子案例是即使你IEnumerable明确地实现(即没有类的公共GetEnumerator方法Test),你也可以拥有一个foreach.

    class Test : IEnumerable
    {
        IEnumerator IEnumerable.GetEnumerator()
        {
    
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这是因为在这种情况下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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    如果类型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
    }
    
    Run Code Online (Sandbox Code Playgroud)

几个有趣的事情需要注意:

  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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

    如果您没有公共实现(即只有显式实现),那么优先级就像IEnumerator<T>> IEnumerator.

  3. 有一个转换操作符涉及foreach将collection元素强制转换为类型(在foreach循环本身中指定)的实现.这意味着即使你写了SomethingEnumerator这样的话:

    class SomethingEnumerator
    {
        public object Current //returns object this time
        {
            get { return ... }
        }
    
        public bool MoveNext()
        {
    
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    你可以写:

    foreach (Something thing in new Test())
    {
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

    因为Something类型兼容object,通过C#规则,或者换句话说,如果两种类型之间存在显式转换,编译器会允许它.否则编译器会阻止它.实际演员表在运行时执行,可能会或可能不会失败.