如何在C#中实现foreach?

在C#中如何实现foreach

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

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

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

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

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

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

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

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

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

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

  1. 如果类型具有名为GetEnumerator的公共/非静态/非generics/无参数方法,该方法返回具有公共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 ,其中GetEnumerator返回具有公共MoveNext方法和公共Current属性的IEnumerator 但是一个有趣的子案例是即使你明确地实现IEnumerable (即Test类上没有公共的GetEnumerator方法),你也可以有一个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那么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; //Current is `T` which is cast statement; } } finally { enumerator.Dispose(); //Enumerator 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 { public SomethingEnumerator GetEnumerator() { //this one is called } IEnumerator IEnumerable.GetEnumerator() { } } 

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

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

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

    你可以写:

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

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