var关键字并不总是有效?
C#,VS 2010.有人,请解释为什么我不能在下面的代码中使用var
!
var props = TypeDescriptor.GetProperties(adapter); // error CS1061: 'object' does not contain a definition for 'DisplayName' foreach (var prop in props) { string name = prop.DisplayName; } // No error foreach (PropertyDescriptor prop in props) { string name = prop.DisplayName; }
TypeDescriptor.GetProperties
返回带有PropertyDescriptorCollection
实例的PropertyDescriptor
。 为什么编译器不能看到这个?
TypeDescriptor.GetProperties
返回一个只有GetEnumerator
实现的类,该实现返回非genericsIEnumerator
。 其Current
属性的类型是object
– 这是编译器可以推断的唯一类型,因此这是prop
变量的类型。
使用PropertyDescriptor
而不是var
作为prop
类型的第二个foreach
实际上执行从object
到PropertyDescriptor
的转换。
假设这段代码:
PropertyDescriptor x = // get from somewhere; object o = x; PropertyDescriptor y = (PropertyDescriptor)o;
每个项目的第二个foreach
循环都会发生同样的情况。
您可以添加Cast
以获取具有GetEnumerator
实现的通用IEnumerable
,该实现返回IEnumerator
。 如果这样做,您可以在foreach
循环中使用var
:
var props = TypeDescriptor.GetProperties(adapter).Cast(); // No error foreach (var prop in props) { string name = prop.DisplayName; }
PropertyDescriptorCollection仅实现IEnumerable
,因此编译器只知道其中包含的元素是object
类型。 在foreach循环中指定类型时,编译器将向您指定的类型插入强制转换。
您可以在IEnumerable
上使用Cast
扩展方法将每个项目Cast
转换为给定类型:
using System.Linq; ... IEnumerable descriptors = props.Cast ();
因为TypeDescriptor.GetProperties
返回一个PropertyDescriptorCollection
,它不实现IEnumerable
但只实现IEnumerable
。
因此prop
只是一个在编译时没有DisplayName
属性的object
。
所以你必须在foreach
明确指定类型:
foreach (PropertyDescriptor prop in props) { string name = prop.DisplayName; }
PropertyDescriptorCollection
实现了IEnumerable
而不是 IEnumerable
所以可以看到的是它枚举了object
s。 这就是var
推断。
foreach
总是能够对其迭代变量类型执行隐藏的强制转换,因此这就是第二个版本工作的原因。 它必须在pre-generics时代以这种方式工作。
我将在这里介绍的内容在C#语言规范的“foreach声明”部分中有详细描述。
当你在foreach
语句(即var
)中使用隐式类型的迭代变量时,这是一件非常好的事情,这里是编译器如何找出var
的意思。
首先,它将表达式的编译时类型检查到foreach
in
关键字的右侧。 (如果它是像PropertyDescriptor[]
或PropertyDescriptor[,,,]
或类似的数组类型,则适用特殊规则。如果它是dynamic
还有另一条规则。)
它检查该类型是否具有一个名为GetEnumerator
(带有该大小写)的方法,该方法具有public
,非静态,非generics的重载并且接受零参数。 如果是这样,它会检查此方法的返回类型(我们仍在讨论编译时类型,因此它是声明的返回类型)。 此类型必须具有方法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); } } }
在我的情况下,由于Current
属性的类型,你会看到var
变成int
( System.Int32
)。
现在在你的情况下,编译时类型的props
是PropertyDescriptorCollection
。 该类型有一个GetEnumerator()
,它是公共的,非静态的。 在这种情况下,该方法的返回类型被视为System.Collections.IEnumerator
。 此IEnumerator
类型具有必需的Current
属性,并且此属性的类型被视为Object
。 所以它来自!
(最初为.NET 1编写的很多类都有这种设计。没有强大的类型与foreach
一起使用。)
注意, 如果一个类型实现IEnumerable
(generics)或/和IEnumerable
(非generics),并且如果这两个接口中的一个“隐式”实现(通常,不是显式接口实现),那么类型肯定有一个GetEnumerator
,它是公共的和非静态的,非generics的并且不带参数。 因此, foreach
将使用公共方法。
现在,如果您尝试foreach
的表达式的编译时类型没有公共实例方法GetEnumerator()
(没有类型参数和没有值参数),编译器将看到该类型是否可以转换为IEnumerable
或(否则)到IEnumerable
。 由于IEnumerable
在T
是协变的,因此通常会有许多Something
以便IEnumerable
适用。 这在规范(版本5.0)中有点令人困惑地解释。 因为它也可能是这样的:
class Foreachable : IEnumerable, IEnumerable { // lots of stuff goes here }
对于具有预期inheritance关系的引用类型Animal
和Giraffe
,并且从规范版本5.0中不清楚此类(编译时类型)无法进行foreach
。