Дженерики, Наследование и Кастинг
Мой вопрос связан с приведением классов внутри общего типа. Хотя я понимаю, что такие объекты, как 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
Допустим, у меня есть объект List. Затем я бросил его в список. В List есть метод add, который принимает аргумент T. Я добавляю в него яблоко (так как у меня есть список). Однако в некоторых местах мой код все еще ожидает List, поэтому некоторые члены List на самом деле являются яблоками!!!
Таким образом, TypeA не может быть приведен к TypeA, даже если X наследует от Y.
Но ConsoleApplication1.TypeA<ConsoleApplication1.TypeC>
не наследуется от ConsoleApplication1.TypeA<ConsoleApplication1.TypeB<int>>