Ключевое слово var не всегда работает?

С#, 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 возвращает a PropertyDescriptorCollection с экземплярами PropertyDescriptor. Почему компилятор не видит этого?

Ответ 1

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;
}

Ответ 2

PropertyDescriptorCollection реализует только IEnumerable, поэтому компилятор знает только, что содержащиеся в нем элементы имеют тип object. Когда вы укажете тип в вашем цикле foreach, компилятор будет вставить приведение в указанный вами тип.

Вы можете использовать метод расширения Cast<T> в IEnumerable для приведения каждого элемента в заданный тип:

using System.Linq;
...
IEnumerable<PropertyDescriptor> descriptors = props.Cast<PropertyDescriptor>();

Ответ 3

Потому что TypeDescriptor.GetProperties возвращает PropertyDescriptorCollection, который не реализует IEnumerable<PropertyDescriptor>, а только IEnumerable.

Поэтому prop - это просто object, у которого нет свойства DisplayName во время компиляции.

Итак, вы должны явно указать тип в foreach:

foreach (PropertyDescriptor prop in props)
{
    string name = prop.DisplayName;
}

Ответ 4

PropertyDescriptorCollection реализует IEnumerable, но не IEnumerable<PropertyDescriptor>, поэтому все, что можно увидеть, это то, что оно перечисляет object s. Итак, что var появляется.

foreach всегда обладал способностью выполнять скрытый прилив, независимо от его типа переменной итерации, поэтому почему вторая версия работает. Он должен был работать таким образом в эпоху, предшествующую дженерикам.

Ответ 5

То, что я расскажу здесь, подробно описано в Спецификации языка С#, раздел "Инструкция foreach".

Когда вы используете неявно типизированную переменную итерации в выражении foreach (т.е. var), что очень хорошо, вот как компилятор узнает, что означает var.

Сначала он проверяет тип выражения compile-time выражения в правой части ключевого слова 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 (не общий), и если один из этих двух интерфейсов реализован "неявно" (обычно, не явная реализация интерфейса), тогда тип, безусловно, имеет GetEnumerator, который является открытым и нестатическим, не общим и принимает нулевые аргументы. Таким образом, открытый метод будет использоваться 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 и Giraffe с ожидаемым отношением наследования, и из спецификации версии 5.0 не ясно, что этот класс (тип времени компиляции) не может быть foreach изд.