Почему IEnumerable <struct>не может быть преобразован в IEnumerable <object>? - PullRequest
28 голосов
/ 13 марта 2012

Почему последняя строка не разрешена?

IEnumerable<double> doubleenumerable = new List<double> { 1, 2 };
IEnumerable<string> stringenumerable = new List<string> { "a", "b" };
IEnumerable<object> objects1 = stringenumerable; // OK
IEnumerable<object> objects2 = doubleenumerable; // Not allowed

Это потому, что double - это тип значения, который не является производным от объекта, следовательно, ковариация не работает?

Означает ли это, что нет способа заставить эту работу:

public interface IMyInterface<out T>
{
    string Method(); 
}

public class MyClass<U> : IMyInterface<U>
{
    public string Method()
    {
        return "test";
    }
}

public class Test
{
    public static object test2() 
    {
        IMyInterface<double> a = new MyClass<double>();
        IMyInterface<object> b = a; // Invalid cast!
        return b.Method();
    }
}

И что мне нужно написать свой собственный IMyInterface<T>.Cast<U>(), чтобы сделать это?

Ответы [ 3 ]

46 голосов
/ 13 марта 2012

Почему последняя строка не разрешена?

Поскольку double является типом значения, а объект является ссылочным типом;ковариация работает только тогда, когда оба типа являются ссылочными типами.

Это потому, что double - это тип значения, который не является производным от объекта, следовательно, ковариация не работает?

Нет.Двойник происходит от объекта.Все типы значений происходят от объекта.

Теперь вопрос, который вы должны были задать:

Почему ковариация не работает для преобразования IEnumerable<double> в IEnumerable<object>?

Потому что кто занимается боксом ?Преобразование из double в объект должно box double.Предположим, у вас есть вызов IEnumerator<object>.Current, который "действительно" является вызовом реализации IEnumerator<double>.Current.Вызывающий ожидает, что объект будет возвращен.Колли возвращается дважды. Где находится код, который выполняет инструкцию по боксу, которая превращает двойное число, возвращаемое IEnumerator<double>.Current в двойное число в штучной упаковке?

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

Если вы хотите, чтобы код, который помечал , выполнял , то в какой-то момент он должен был быть записан ,и вы тот человек, который хочет это написать.Самый простой способ - использовать метод расширения Cast<T>:

IEnumerable<object> objects2 = doubleenumerable.Cast<object>();

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

ОБНОВЛЕНИЕ: комментатор отмечает, что я задал вопрос - то есть я ответил на вопрос, предполагая существование механизма, который решает проблему так же трудно, как требует решение исходного вопроса.Каким образом реализация Cast<T> позволяет решить проблему с определением, в коробку или нет?

Это работает как этот скетч.Обратите внимание, что типы параметров не generic:

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{
    if (sequence == null) throw ...
    if (sequence is IEnumerable<T>) 
        return sequence as IEnumerable<T>;
    return ReallyCast<T>(sequence);
}

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence)
{
    foreach(object item in sequence)
        yield return (T)item;
}

Ответственность за определение того, является ли преобразование из объекта в T преобразованием распаковки или ссылочным преобразованием, переносится на среду выполнения.Джиттер знает, является ли T ссылочным типом или типом значения.В 99% случаев это будет ссылочный тип.

5 голосов
/ 14 марта 2012

Чтобы понять, что разрешено и не разрешено, и почему вещи ведут себя так, как они делают, полезно понять, что происходит под капотом. Для каждого типа значения существует соответствующий тип объекта класса, который, как и все объекты, будет наследоваться от System.Object. Каждый объект класса включает в свои данные 32-битное слово (x86) или 64-битное длинное слово (x64), которое идентифицирует его тип. Однако хранилища типа значения не содержат таких объектов класса или ссылок на них, а также не содержат данных типа данных, хранящихся в них. Вместо этого каждое местоположение типа примитивного значения просто содержит биты, необходимые для представления значения, а каждое местоположение хранения типа структурного значения просто содержит содержимое всех открытых и закрытых полей этого типа.

Когда кто-то копирует переменную типа Double в переменную типа Object, он создает новый экземпляр типа объекта класса, связанный с Double, и копирует все байты из оригинала в этот новый объект класса. , Хотя тип класса в штучной упаковке Double имеет то же имя, что и тип значения Double, это не приводит к неоднозначности, поскольку их обычно нельзя использовать в одном и том же контексте. Места хранения типов значений содержат необработанные биты или комбинации полей без хранимой информации о типах; копирование одного такого хранилища в другое копирует все байты и, следовательно, копирует все открытые и закрытые поля. Напротив, объекты кучи типов, производных от типов значений, являются объектами кучи и ведут себя как объекты кучи. Хотя C # рассматривает содержимое хранилищ с типами значений как производные от Object, содержимое таких хранилищ - это просто наборы байтов, фактически вне системы типов. Поскольку к ним можно получить доступ только с помощью кода, который знает, что представляют байты, нет необходимости хранить такую ​​информацию в самом месте хранения. Хотя необходимость в боксе при вызове GetType для структуры часто описывается в терминах GetType как не затененной, не виртуальной функции, реальная необходимость проистекает из того факта, что содержимое места хранения типа значения (в отличие от самого местоположения) не имеют информации о типе.

0 голосов
/ 13 марта 2012

Дисперсия этого типа поддерживается только для ссылочных типов. Смотри http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

...