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有一些有趣的皱纹:
IEnumeratorforeach将在最后处置迭代器; 这是简单的IEnumerator<T>延伸IDisposable,但IEnumerator 并不时,编译器插入检查在执行时测试是否迭代器工具IDisposableIEnumerable或者IEnumerable<T>只要您有适用的GetEnumerator()方法返回具有合适Current和MoveNext()成员的类型.如注释中所述,类型也可以实现IEnumerable或IEnumerable<T>显式,但是具有GetEnumerator()返回除IEnumerator/ 之外的类型的公共方法IEnumerator<T>.请参阅List<T>.GetEnumerator()示例 - 这可以避免在许多情况下不必要地创建引用类型对象.有关详细信息,请参阅C#4规范的第8.8.4节.
naw*_*fal 11
惊讶于没有触及确切的实施.虽然您在问题中发布的内容是最简单的形式,但完整的实现(包括枚举器处理,转换等)在规范的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
{
}
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)如果类型实现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)几个有趣的事情需要注意:
在上述两种情况下,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实现并不像表面上看起来那么简单)
枚举器优先级 - 就像你有一个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.
有一个转换操作符涉及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#规则,或者换句话说,如果两种类型之间存在显式转换,编译器会允许它.否则编译器会阻止它.实际演员表在运行时执行,可能会或可能不会失败.