Дженерики, Наследование и Кастинг

Мой вопрос связан с приведением классов внутри общего типа. Хотя я понимаю, что такие объекты, как List<string> в List<object> требуется поддержка ковариации, чтобы предотвратить добавление object Объекты в списке, который содержит строки, мне интересно, почему приведение, подобное приведенному ниже, не принимается компилятором и является ли оно разрешимым с использованием интерфейсов и совместной или контравариантности:

public class TypeA<T> {}

public class TypeB<T> {}

public class TypeC : TypeB<int> {}

class Program
{
    public static void MyMethod<OutputType>(TypeA<TypeB<OutputType>> Parameter) {}

    static void Main(string[] args)
    {
        TypeA<TypeC> Test = new TypeA<TypeC>();
        MyMethod<int>(Test);
    }
}

Компиляция приводит к ошибке:

Argument 1: cannot convert from 'ConsoleApplication1.TypeA<ConsoleApplication1.TypeC>' to 'ConsoleApplication1.TypeA<ConsoleApplication1.TypeB<int>>',

даже если TypeC является прямым потомком TypeB<int>

6 ответов

Решение

Итак, поскольку C# 4.0 поддерживает ковариацию для интерфейсов с обобщениями, следующий пример может решить проблему:

public interface ITypeA<out T> {}

public class TypeA<T> : ITypeA<T> {}

public class TypeB<T> {}

public class TypeC : TypeB<int> {}

class Program
{
    public static void MyMethod<OutputType>(ITypeA<TypeB<OutputType>> Parameter) {}

    static void Main(string[] args)
    {
        ITypeA<TypeC> Test = new TypeA<TypeC>();
        MyMethod<int>(Test);
    }
}

Обратите внимание, что тип T может использоваться только для возвращаемых значений из методов в ITypeA.

Меня довольно смущает ваш вопрос, поскольку преамбула к этому вопросу показывает, что вы знаете ответ. Вы не можете конвертировать List<string> к List<object> потому что тогда вы можете добавить жирафа в список объектов, который действительно является списком строк, который не является безопасным для типов.

Если вы замените "TypeA" на "List", TypeB<int> с "object" и "TypeC" с "string" вы преобразовали свою ситуацию в ситуацию, которая, как вы уже знаете, не работает по уважительным причинам. Дело не в том, что компилятор волшебным образом знает что-то о List и просто не допускает сценарий List - скорее, компилятор не допускает таких отклонений.

В C# 4 мы добавляем дисперсию к некоторым интерфейсам и типам делегатов, которые, как известно, безопасны при использовании дисперсии. Это модель "согласия" - вы должны доказать компилятору, что вы в безопасности, и только тогда мы позволим вам использовать дисперсию.

Как отмечали другие комментаторы, несмотря на то, что TypeC происходит от TypeB<int>это не правда, что TypeA<TypeC> происходит от TypeA<TypeB<int>>, Однако вы, вероятно, можете заставить свой код работать, добавив дополнительный параметр типа в MyMethod:

public static void MyMethod<DerivedB,OutputType>(TypeA<DerivedB> Parameter)
    where DerivedB : TypeB<OutputType> {}

Еще одна проблема ковариации дженериков - см. Ответ, который я разместил здесь. TypeA никак не наследуется от TypeA>

Допустим, у меня есть объект List. Затем я бросил его в список. В List есть метод add, который принимает аргумент T. Я добавляю в него яблоко (так как у меня есть список). Однако в некоторых местах мой код все еще ожидает List, поэтому некоторые члены List на самом деле являются яблоками!!!

Таким образом, TypeA не может быть приведен к TypeA, даже если X наследует от Y.

Но ConsoleApplication1.TypeA<ConsoleApplication1.TypeC> не наследуется от ConsoleApplication1.TypeA<ConsoleApplication1.TypeB<int>>

Другие вопросы по тегам