Вопрос о ковариации дженериков С# 4.0

Определив этот интерфейс:

public interface IInputBoxService<out T> {
    bool ShowDialog();
    T Result { get; }
}

Почему работает следующий код:

public class StringInputBoxService : IInputBoxService<string> {
    ...
}

...

IInputBoxService<object> service = new StringInputBoxService();

и это не так::

public class IntegerInputBoxService : IInputBoxService<int> {
    ...
}

...

IInputBoxService<object> service = new IntegerInputBoxService();

Имеет ли это какое-либо отношение к int, являющемуся типом значения? Если да, то как я могу обойти эту ситуацию?

Спасибо

Ответы

Ответ 1

Да, это абсолютно связано с int, являющимся типом значения. Общая дисперсия в С# 4 работает только со ссылочными типами. Это связано прежде всего с тем, что ссылки всегда имеют одно и то же представление: ссылка является просто ссылкой, поэтому CLR может использовать одни и те же биты для того, что она знает, является ссылкой на строку как для ссылки на объект. CLR может убедиться, что код будет безопасным, и использовать собственный код, который знает только о IInputBoxService<object> при передаче IInputBoxService<string> - значение, возвращаемое из Result, будет совместимым с представлением (если такой термин существует!).

С int = > object там должен быть бокс и т.д., поэтому вы не получите тот же код, который в основном меняет дисперсию.

EDIT: спецификация С# 4.0 говорит об этом в разделе 13.1.3.2:

Цель аннотаций дисперсии - для обеспечения более мягкого (но все же тип безопасного) преобразования в интерфейс и типы делегатов. С этой целью определения неявного (§6.1) и явные преобразования (§6.2) используют понятия дисперсия-конвертируемость, которая определяется следующим образом: Тип T является дисперсионно-конвертируемым для типа T, если T либо интерфейс или объявленный тип делегата с вариантами типа T, а для каждого варианта параметр Xi одно из следующих имеет место:

  • Xi ковариантно и неявная ссылка или идентификация существует преобразование от Ai до Bi

  • Си является контравариантным и неявным ссылка или конверсия идентичности существует от Bi до Ai

  • Xi инвариантен и преобразование идентичности существует из Ai to Bi

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

Что касается обходных путей: я думаю, вам придется создать свой собственный класс-оболочку, в основном. Это может быть просто:

public class Wrapper<T>
{
    public T Value { get; private set; }
    public Wrapper(T value)
    {
        Value = value;
    }
}

Это довольно неприятно: (