Полученный ответ (о том, что нет никаких различий в отношении типов значений) является правильным. Причина, по которой ковариантность и контравариантность не работают, когда один из аргументов переменного типа является типом значения, заключается в следующем. Предположим, что это сработало и показало, как все идет ужасно неправильно:
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 вы можете преобразовать делегата, возвращающего int, в делегата, возвращающего объект. VB просто создает второй делегат, который оборачивает вызов первого делегата и упаковывает результат. VB решает отказаться от ограничения, согласно которому преобразование ссылок сохраняет идентичность объекта.
Это иллюстрирует интересную разницу в принципах проектирования C# и VB. В C# команда разработчиков всегда думает: «Как компилятор может найти то, что может быть ошибкой в программе пользователя, и обратить на это их внимание?» и команда VB думает: «Как мы можем выяснить, что, вероятно, имел в виду пользователь, и просто сделать это от его имени?» Короче говоря, философия C# такова: «Если ты что-то видишь, скажи что-нибудь», а философия VB — «делай то, что я имею в виду, а не то, что я говорю». Обе являются совершенно разумными философиями; интересно наблюдать, как два языка с почти идентичными наборами функций различаются в этих мелких деталях из-за принципов проектирования.
04.11.2010