Почему делегирование контравариантности не работает с типами значений? - PullRequest
14 голосов
/ 04 ноября 2010

Этот фрагмент не скомпилирован в LINQPad.

void Main()
{
    (new[]{0,1,2,3}).Where(IsNull).Dump();
}

static bool IsNull(object arg) { return arg == null; }

Сообщение об ошибке компилятора:

Нет перегрузки для 'UserQuery.IsNull (объект)' соответствует делегату 'System.Func'

Работает для строкового массива, но не работает для int[]. По-видимому, это связано с боксом, но я хочу знать подробности.

Ответы [ 3 ]

41 голосов
/ 04 ноября 2010

Ответ (что нет различий с типами значений) является правильным.Ковариация и контравариантность причины не работают, когда один из аргументов переменного типа является типом значения, заключается в следующем.Предположим, это сработало и покажет, как все идет ужасно неправильно:

Func<int> f1 = ()=>123;
Func<object> f2 = f1; // Suppose this were legal.
object ob = f2();

ОК, что происходит?f2 является идентичным ссылке f1.Поэтому, что бы ни делала f1, делает f2.Что делает f1?Он помещает 32-разрядное целое число в стек.Что делает задание?Он берет все, что находится в стеке, и сохраняет его в переменной «ob».

Где была инструкция по боксу? Не было ни одного!Мы просто сохранили 32-разрядное целое число в хранилище, которое ожидало не целое число, а 64-разрядный указатель на местоположение кучи, содержащее целое в штучной упаковке.Таким образом, вы просто сместили стек и испортили содержимое переменной с неверной ссылкой.Вскоре процесс затихнет.

Так куда же должна идти инструкция по боксу?Компилятор должен где-то сгенерировать инструкцию по боксу.Он не может идти после вызова f2, потому что компилятор считает, что f2 возвращает объект, который уже был упакован.Он не может перейти к вызову f1, потому что f1 возвращает int, а не упакованный int.Он не может идти между вызовом f2 и вызовом f1 , потому что они один и тот же делегат;между 1011 * нет.

Единственное, что мы могли бы здесь сделать, это заставить вторую строку на самом деле означать:

Func<object> f2 = ()=>(object)f1();

и теперь у нас больше нет ссылочной тождественности между f1 и f2, поэтому чтоэто точка отклонения ?Весь смысл наличия ковариантных эталонных преобразований состоит в том, чтобы сохранить эталонную идентификацию .

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

ОБНОВЛЕНИЕ: в моем ответе я должен был отметить, что в VB вы можете преобразоватьвозвращающий int делегат возвращающему объект делегату.VB просто создает второй делегат, который переносит вызов к первому делегату и упаковывает результат.VB решает отказаться от ограничения на то, что ссылочное преобразование сохраняет идентичность объекта.

Это иллюстрирует интересную разницу в философии дизайна C # и VB.В C # команда разработчиков всегда думает: «Как компилятор может найти ошибку в программе пользователя и довести ее до сведения?»и команда VB думает: «Как мы можем выяснить, что пользователь, вероятно, имел в виду, и просто сделать это от его имени?»Короче говоря, философия C # - «если ты что-то видишь, скажи что-нибудь», а философия VB - «делай, что я имею в виду, а не то, что я говорю».Оба - совершенно разумные философии;Интересно видеть, как два языка, которые имеют почти идентичные наборы функций, отличаются этими маленькими деталями из-за принципов проектирования.

3 голосов
/ 04 ноября 2010

Потому что Int32 является типом значения, а контрастность не работает для типов значений.

Вы можете попробовать это:

(new **object**[]{0,1,2,3}).Where(IsNull).Dump();
0 голосов
/ 04 ноября 2010

Это не работает для int, потому что нет объектов.

Попробуйте:

void Fun()
{
    IEnumerable<object> objects = (new object[] { 0, 1, null, 3 }).Where(IsNull);

    foreach (object item in objects)
    {
        Console.WriteLine("item is null");
    }
}

bool IsNull(object arg) { return arg == null; }
...