Вопрос о ковариантности дженериков в C # 4.0 - PullRequest
13 голосов
/ 28 апреля 2010

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

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 Ответ

14 голосов
/ 28 апреля 2010

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

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

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

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

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

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

  • Си инвариантен и преобразование идентичности существует из От Аи до Би

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

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

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

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

...