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实际上执行从objectPropertyDescriptor的转换。

假设这段代码:

 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变成intSystem.Int32 )。

现在在你的情况下,编译时类型的propsPropertyDescriptorCollection 。 该类型有一个GetEnumerator() ,它是公共的,非静态的。 在这种情况下,该方法的返回类型被视为System.Collections.IEnumerator 。 此IEnumerator类型具有必需的Current属性,并且此属性的类型被视为Object 。 所以它来自!

(最初为.NET 1编写的很多类都有这种设计。没有强大的类型与foreach一起使用。)

注意, 如果一个类型实现IEnumerable (generics)或/和IEnumerable (非generics),并且如果这两个接口中的一个“隐式”实现(通常,不是显式接口实现),那么类型肯定有一个GetEnumerator ,它是公共的和非静态的,非generics的并且不带参数。 因此, foreach将使用公共方法。

现在,如果您尝试foreach的表达式的编译时类型没有公共实例方法GetEnumerator() (没有类型参数和没有值参数),编译器将看到该类型是否可以转换为IEnumerable或(否则)到IEnumerable 。 由于IEnumerableT是协变的,因此通常会有许多Something以便IEnumerable适用。 这在规范(版本5.0)中有点令人困惑地解释。 因为它也可能是这样的:

  class Foreachable : IEnumerable, IEnumerable { // lots of stuff goes here } 

对于具有预期inheritance关系的引用类型AnimalGiraffe ,并且从规范版本5.0中不清楚此类(编译时类型)无法进行foreach