var关键字并不总是有效?

时间:2013-02-14 11:43:17

标签: c# var propertydescriptor

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。为什么编译器不能看到这个?

5 个答案:

答案 0 :(得分:23)

TypeDescriptor.GetProperties返回一个只有GetEnumerator实现的类,它返回非泛型IEnumerator。它的Current属性的类型是object - 这是编译器可以推断的唯一类型,因此这是prop变量的类型。

使用foreach代替PropertyDescriptor作为var类型的第二个prop实际执行从objectPropertyDescriptor的转换。

假设此代码:

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的意思。

首先,它会检查inforeach关键字右侧的表达式的编译时间类型。 (如果它是类似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.Int32Current)。

现在,在您的情况下,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
    }

对于具有预期继承关系的引用类型AnimalGiraffe,并且从规范版本5.0 中不清楚此类(编译时类型)是否可以不是foreach ed。