2010-11-04 1 views
12

Этот фрагмент не скомпилирован в LINQPad.Почему не делегировать работу по контравариантности со значениями?

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

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

сообщение об ошибке компилятора заключается в следующем:

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

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

+0

Должно ли это быть. Где (x => IsNull (x)) '? –

+0

@ Джоэль Эфиртон: То же самое (почти). – leppie

+0

Попробуйте сделать 'IsNull' общим. Лом, что это то, что вы просите :) – leppie

ответ

38

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

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 , потому что они являются одним и тем же делегатом; между «» нет.

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

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

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

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

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

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

0

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

Try:

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

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

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

(new **object**[]{0,1,2,3}).Where(IsNull).Dump(); 
+2

Dump() может быть методом расширения – Konstantin

Смежные вопросы