Подтвердить что ты не робот

Почему IEnumerable <struct> не может быть отличен как IEnumerable <object>?

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

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>() для этого?

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

Он работает как этот эскиз. Обратите внимание, что типы параметров не являются общими:

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% времени, конечно, будет ссылочным типом.

Ответ 2

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

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