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
。为什么编译器不能看到这个?
答案 0 :(得分:23)
TypeDescriptor.GetProperties
返回一个只有GetEnumerator
实现的类,它返回非泛型IEnumerator
。它的Current
属性的类型是object
- 这是编译器可以推断的唯一类型,因此这是prop
变量的类型。
使用foreach
代替PropertyDescriptor
作为var
类型的第二个prop
实际执行从object
到PropertyDescriptor
的转换。
假设此代码:
PropertyDescriptor x = // get from somewhere;
object o = x;
PropertyDescriptor y = (PropertyDescriptor)o;
每个项目的第二个foreach
循环都会发生同样的情况。
您可以添加Cast<PropertyDescriptor>()
来获取通用IEnumerable<T>
,其GetEnumerator
的实现返回IEnumerator<T>
。如果您这样做,可以在var
循环中使用foreach
:
var props = TypeDescriptor.GetProperties(adapter).Cast<PropertyDescriptor>();
// No error
foreach (var prop in props)
{
string name = prop.DisplayName;
}
答案 1 :(得分:8)
PropertyDescriptorCollection仅实现IEnumerable
,因此编译器只知道其中包含的元素的类型为object
。在foreach循环中指定类型时,编译器将向您指定的类型插入强制转换。
您可以使用Cast<T>
上的IEnumerable
扩展程序将每个项目投放到给定类型:
using System.Linq;
...
IEnumerable<PropertyDescriptor> descriptors = props.Cast<PropertyDescriptor>();
答案 2 :(得分:5)
由于TypeDescriptor.GetProperties
返回的PropertyDescriptorCollection
未实现IEnumerable<PropertyDescriptor>
,只有IEnumerable
。
因此prop
只是object
,在编译时没有DisplayName
属性。
因此,您必须在foreach
:
foreach (PropertyDescriptor prop in props)
{
string name = prop.DisplayName;
}
答案 3 :(得分:3)
PropertyDescriptorCollection
实现IEnumerable
但不 IEnumerable<PropertyDescriptor>
所以可以看出它列举了object
个。这就是var
推断的内容。
foreach
始终能够对其迭代变量类型执行隐藏转换,因此这就是第二个版本的工作原理。它必须在仿制前时代以这种方式工作。
答案 4 :(得分:2)
我将在此处介绍的内容将在C#语言规范的正式详细信息中进行描述,“the foreach statement”。
在foreach
语句中使用隐式类型的迭代变量时(即var
),这是一件非常好的事情,这里是编译器的方式找出var
的意思。
首先,它会检查in
中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
(非通用), if 其中一个两个接口是“隐式”实现的(通常不是显式接口实现),然后类型肯定有一个GetEnumerator
,它是公共的和非静态的,非泛型的并且接受零参数。因此foreach
将使用公共方法。
现在,如果您尝试foreach
的表达式的编译时类型没有公共实例方法GetEnumerator()
(没有类型参数且没有值参数),编译器将查看是否类型可以转换为IEnumerable<Something>
或(else)到IEnumerable
。由于IEnumerable<out T>
中的T
是协变的,因此通常会有很多Something
,因此适用IEnumerable<Something>
。这在规范(版本5.0)中有点令人困惑地解释。因为类型也可能如下所示:
class Foreachable : IEnumerable<Animal>, IEnumerable<Giraffe>
{
// lots of stuff goes here
}
对于具有预期继承关系的引用类型Animal
和Giraffe
,并且从规范版本5.0 中不清楚此类(编译时类型)是否可以不是foreach
ed。