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