11

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返回 aPropertyDescriptorCollection的实例PropertyDescriptor。为什么编译器看不到这个?

4

5 回答 5

23

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

foreach使用PropertyDescriptor而不是var类型的第二个prop实际执行从objectto的转换PropertyDescriptor

假设这段代码:

PropertyDescriptor x = // get from somewhere;
object o = x;
PropertyDescriptor y = (PropertyDescriptor)o;

foreach在每个项目的第二个循环中也发生了同样的情况。


您可以添加 a以获取具有该返回的实现的Cast<PropertyDescriptor>()泛型。如果你这样做,你可以在循环中使用:IEnumerable<T>GetEnumeratorIEnumerator<T>varforeach

var props = TypeDescriptor.GetProperties(adapter).Cast<PropertyDescriptor>();

// No error
foreach (var prop in props)
{
    string name = prop.DisplayName;
}
于 2013-02-14T11:46:05.037 回答
8

PropertyDescriptorCollection 只实现IEnumerable,因此编译器只知道其中包含的元素是 type object。当您在 foreach 循环中指定类型时,编译器将插入一个转换为您指定的类型。

您可以使用Cast<T>扩展方法IEnumerable将每个项目转换为给定类型:

using System.Linq;
...
IEnumerable<PropertyDescriptor> descriptors = props.Cast<PropertyDescriptor>();
于 2013-02-14T11:47:12.680 回答
5

因为TypeDescriptor.GetProperties返回一个PropertyDescriptorCollection没有实现IEnumerable<PropertyDescriptor>但只有IEnumerable.

因此prop只是一个在编译时object没有属性的。DisplayName

因此,您必须在以下内容中明确指定类型foreach

foreach (PropertyDescriptor prop in props)
{
    string name = prop.DisplayName;
}
于 2013-02-14T11:46:54.187 回答
3

PropertyDescriptorCollection实现IEnumerable并非 IEnumerable<PropertyDescriptor>如此,所有可以看到的是它枚举了objects。所以这就是var推断。

foreach始终能够对其迭代变量类型执行隐藏强制转换,这就是第二个版本有效的原因。在前泛型时代,它必须以这种方式工作。

于 2013-02-14T11:46:45.613 回答
2

我将在此处介绍的内容在 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.Int32Current

现在在您的情况下,编译时类型propsPropertyDescriptorCollection. 根据需要,该类型具有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

于 2013-02-16T22:07:16.393 回答