foreach
在 C# 中究竟是如何实现的?
我想象它的一部分看起来像:
var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
// do some stuff here
}
但是我不确定到底发生了什么。enumerator.Current
每个周期使用什么方法返回?它是返回 [for each cycle] 还是需要一个匿名函数或其他东西来执行它的主体foreach
?
foreach
在 C# 中究竟是如何实现的?
我想象它的一部分看起来像:
var enumerator = TInput.GetEnumerator();
while(enumerator.MoveNext())
{
// do some stuff here
}
但是我不确定到底发生了什么。enumerator.Current
每个周期使用什么方法返回?它是返回 [for each cycle] 还是需要一个匿名函数或其他东西来执行它的主体foreach
?
它不使用匿名函数,不。基本上,编译器将代码转换为与您在此处显示的 while 循环大致等效的内容。
foreach
不是函数调用 - 它内置于语言本身,就像for
循环和while
循环一样。它不需要返回任何东西或“获取”任何类型的功能。
请注意,foreach
它有一些有趣的皱纹:
IEnumerator
foreach
将在最后处理迭代器;这很简单IEnumerator<T>
which extends IDisposable
,但IEnumerator
不是,编译器插入一个检查以在执行时测试迭代器是否实现IDisposable
IEnumerable
or的类型IEnumerable<T>
,只要您有一个适用的GetEnumerator()
方法,该方法返回具有合适的Current
andMoveNext()
成员的类型。如注释中所述,类型也可以实现IEnumerable
或IEnumerable<T>
显式,但具有返回/GetEnumerator()
以外的类型的公共方法。参见示例 - 这避免了在许多情况下不必要地创建引用类型对象。IEnumerator
IEnumerator<T>
List<T>.GetEnumerator()
有关详细信息,请参阅 C# 4 规范的第 8.8.4 节。
惊讶于没有触及确切的实现。虽然您在问题中发布的内容是最简单的形式,但完整的实现(包括枚举器处理、强制转换等)位于规范的 8.8.4 部分。
现在有两种情况foreach
可以在类型上运行循环:
如果该类型有一个名为 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();
}
如果该类型实现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
}
需要注意的一些有趣的事情是:
在上述两种情况下,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
实现并不像表面上看起来那么容易)
枚举器优先级 - 如果你有一个public GetEnumerator
方法,那么foreach
无论谁在实现它,这都是默认选择。例如:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
如果您没有公共实现(即只有显式实现),则优先级类似于IEnumerator<T>
> IEnumerator
。
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# 规则,或者换句话说,如果两种类型之间可能存在显式转换,编译器就会允许它。否则编译器会阻止它。实际的转换是在运行时执行的,可能会也可能不会失败。