我将在此处介绍的内容在 C# 语言规范的“foreach 语句”部分中进行了正式详细描述。
当您在语句中使用隐式类型的迭代变量foreach
(即var
)时,这是一件非常好的事情,编译器可以通过以下方式找出var
其含义。
首先,它会检查in
. (如果它是类似or或类似foreach
的数组类型,则适用特殊规则。如果是 .,则有另一条规则。)PropertyDescriptor[]
PropertyDescriptor[,,,]
dynamic
它检查该类型是否有一个精确调用的方法GetEnumerator
(使用该大写),其重载是public
,非静态,非泛型并接受零参数。如果是这样,它会检查这个方法的返回类型(我们在这里仍然讨论编译时类型,所以它是声明的返回类型)。这种类型必须有一个方法MoveNext()
和一个属性Current
。然后它获取属性类型Current
并将其用作元素类型。所以你var
代表这种类型。
为了展示它是如何工作的,我写了这个:
class Foreachable
{
public MyEnumeratorType GetEnumerator() // OK, public, non-static, non-generic, zero arguments
{
return default(MyEnumeratorType);
}
}
struct MyEnumeratorType
{
public int Current
{
get { return 42; }
}
public bool MoveNext()
{
return true;
}
}
static class Test
{
static void Main()
{
var coll = new Foreachable();
foreach (var x in coll) // mouse-over 'var' to see it translates to 'int'
{
Console.WriteLine(x);
}
}
}
由于属性的类型,您会看到在我的情况下var
变为int
( )。System.Int32
Current
现在在您的情况下,编译时类型props
是PropertyDescriptorCollection
. 根据需要,该类型具有GetEnumerator()
公共且非静态的类型。System.Collections.IEnumerator
在这种情况下,可以看到方法的返回类型。此IEnumerator
类型具有所需的Current
属性,并且此属性的类型被视为Object
。所以它来自那里!
(最初为 .NET 1 编写的很多类都有这种设计。没有强类型foreach
。)
请注意,如果一个类型实现IEnumerable<out T>
(通用)或/和IEnumerable
(非通用),并且如果这两个接口之一是“隐式”实现的(通常,不是显式接口实现),那么该类型肯定有一个GetEnumerator
是公共的和非-static,非泛型并采用零参数。这样公共方法将被foreach
.
现在,如果您尝试使用的表达式的编译时类型foreach
没有公共实例方法GetEnumerator()
(没有类型参数和值参数),编译器将查看该类型是否可转换为IEnumerable<Something>
或(否则)转换为IEnumerable
. 由于IEnumerable<out T>
在 中是协变的T
,因此通常会有很多Something
这样IEnumerable<Something>
适用。这在规范(5.0 版)中解释得有些混乱。因为类型也可能是这样的:
class Foreachable : IEnumerable<Animal>, IEnumerable<Giraffe>
{
// lots of stuff goes here
}
对于引用类型Animal
并具有预期的继承关系,并且从规范版本 5.0Giraffe
中不清楚该类(编译时类型)不能被编辑。foreach