[英]Covariance with C# Generics
Given an interface IQuestion
and an implementation of that interface AMQuestion
, suppose the following example: 给定一个接口
IQuestion
和接口的实现AMQuestion
,假设下面的例子:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed;
This example yields, as expected, a compile error saying the two are not of the same type. 正如预期的那样,这个例子产生一个编译错误,说明两者的类型不同。 But it states an explicit conversion exists.
但它表明存在明确的转换。 So I change it to look like this:
所以我把它改成这样:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
Which then compiles but, at run time, nonTyped
is always null. 然后编译,但在运行时,
nonTyped
始终为null。 If someone could explain two things: 如果有人可以解释两件事:
It would be greatly appreciated. 这将不胜感激。 Thank you!
谢谢!
The fact that AMQuestion
implements the IQuestion
interface does not translate into List<AMQuestion>
deriving from List<IQuestion>
. AMQuestion
实现IQuestion
接口的事实不会转换为从List<AMQuestion>
派生的List<IQuestion>
。
Because this cast is illegal, your as
operator returns null
. 因为此强制转换是非法的,所以您的
as
运算符返回null
。
You must cast each item individually as such: 您必须单独投射每个项目:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
Regarding your comment, consider the following code, with the usual cliché animal examples: 关于您的评论,请考虑以下代码,以及常见的陈词滥调动物示例:
//Lizard and Donkey inherit from Animal
List<Lizard> lizards = new List<Lizard> { new Lizard() };
List<Donkey> donkeys = new List<Donkey> { new Donkey() };
List<Animal> animals = lizards as List<Animal>; //let's pretend this doesn't return null
animals.Add(new Donkey()); //Reality unravels!
if we were allowed to cast List<Lizard>
to a List<Animal>
, then we could theoretically add a new Donkey
to that list, which would break inheritance. 如果我们被允许将
List<Lizard>
转换为List<Animal>
,那么理论上我们可以在该列表中添加一个新的Donkey
,这将破坏继承。
Why it doesn't work: as
returns null
if the value's dynamic type cannot be casted to the target type, and List<AMQuestion>
cannot be casted to IList<IQuestion>
. 为什么它不工作:
as
返回null
,如果该值的动态类型不能被浇铸成目标类型,并List<AMQuestion>
不能被强制转换为IList<IQuestion>
But why can't it? 但为什么不能呢? Well, check it:
好吧,检查一下:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
nonTyped.Add(new OTQuestion());
AMQuestion whaaaat = typed[0];
IList<IQuestion>
says "You can add any IQuestion
to me". IList<IQuestion>
说“你可以向我添加任何IQuestion
”。 But that's a promise it couldn't keep if it were a List<AMQuestion>
. 但如果它是
List<AMQuestion>
这是一个它无法保留的承诺。
Now, if you didn't want to add anything, just view it as a collection of IQuestion
-compatible things, then the best thing to do would be to cast it to an IReadOnlyList<IQuestion>
with List.AsReadOnly
. 现在,如果您不想添加任何内容,只需将其视为
IQuestion
兼容事物的集合,那么最好的做法是将其转换为带有List.AsReadOnly
的IReadOnlyList<IQuestion>
。 Since a read-only list can't have strange things added to it, it can be casted properly. 由于只读列表不能添加奇怪的内容,因此可以正确地进行转换。
The issue is that List<AMQuestion>
cannot be cast to IList<IQuestion>
, so using the as
operator does not help. 问题是
List<AMQuestion>
无法List<AMQuestion>
为IList<IQuestion>
,因此使用as
运算符无济于事。 Explicit conversion in this case means to cast AMQuestion
to IQuestion
: 在这种情况下显式转换意味着将
AMQuestion
为IQuestion
:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();
By the way, you have the term "Covariance" in your title. 顺便说一句,你的标题中有“协方差”一词。 In
IList
the type is not covariant. 在
IList
,类型不是协变的。 This is exactly why the cast does not exist. 这正是演员阵容不存在的原因。 The reason is that the
IList
interface has T
in some parameteres and in some return values, so neither in
nor out
can be used for T
. 其原因是,该
IList
接口具有T
在一些parameteres 并且在一些返回值,所以既没有in
也不out
可用于T
。 (@Sneftel has a nice example to show why this cast is not allowed.) (@Sneftel有一个很好的例子来说明为什么不允许这种演员表。)
If you only need to read from the list, you can use IEnumerable
instead: 如果您只需要从列表中读取,则可以使用
IEnumerable
:
IEnumerable<IQuestion> = typed;
This will work because IEnumerable<out T>
has out
defined, since you can't pass it a T
as parameter. 这将工作,因为
IEnumerable<out T>
拥有out
定义,因为你不能传递一个T
的参数。 You should usually make the weakest "promise" possible in your code to keep it extensible. 您通常应该在代码中使最弱的“承诺”保持可扩展性。
IList<T>
is not covariant for T
; IList<T>
对于T
不是协变的; it can't be, as the interface defines functions that take values of type T
in an "input" position. 它不能,因为接口定义了在“输入”位置采用类型
T
值的函数。 However, IEnumerable<T>
is covariant for T
. 但是,
IEnumerable<T>
对于T
是协变的。 If you can limit your type to IEnumerable<T>
, you can do this: 如果您可以将类型限制为
IEnumerable<T>
,则可以执行以下操作:
List<AMQuestion> typed = new List<AMQuestion>();
IEnumerable<IQuestion> nonTyped = typed;
This does not do any conversions on the list. 这不会对列表进行任何转换。
The reason you cannot convert a List<AMQuestion>
to a List<IQuestion>
(assuming AMQuestion implements the interface) is that there would have to be several runtime checks on functions like List<T>.Add
, to make sure you were really adding an AMQuestion
. 你无法将
List<AMQuestion>
转换为List<IQuestion>
(假设AMQuestion实现了接口)的原因是必须对List<T>.Add
等函数进行多次运行时检查,以确保你真正添加AMQuestion
。
The "as" operator will always return null there as no valid cast exists - this is defined behavior. “as”运算符将始终返回null,因为不存在有效的强制转换 - 这是已定义的行为。 You have to convert or cast the list like this:
您必须像这样转换或转换列表:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
A type with a generic type parameter can only be covariant if this generic type occurs only in read accesses and contravariant, if it occurs only in write accesses. 具有泛型类型参数的类型只能是协变的,如果此泛型类型仅出现在读访问和逆变中,如果它仅出现在写访问中。
IList<T>
allows both, read and write access to values of type T
, so it cannot be variant! IList<T>
允许对T
类型的值进行读写访问,因此它不能变体!
Let's assume that you were allowed to assign a List<AMQuestion>
to a variable of type IList<IQuestion>
. 假设您被允许将
List<AMQuestion>
分配给IList<IQuestion>
类型的变量。 Now let's implement a class XYQuestion : IQuestion
and insert a value of that type into our IList<IQuestion>
, which seems perfectly legal. 现在让我们实现一个
class XYQuestion : IQuestion
并在我们的IList<IQuestion>
插入该类型的值,这似乎是完全合法的。 This list still references a List<AMQuestion>
, but we cannot insert a XYQuestion
into a List<AMQuestion>
! 此列表仍引用
List<AMQuestion>
,但我们无法将XYQuestion
插入List<AMQuestion>
! Therefore the two list types are not assignment compatible. 因此,两种列表类型不兼容。
IList<IQuestion> list = new List<AMQuestion>(); // Not allowed!
list.Add(new XYQuestion()); // Uuups!
Because List<T>
is not a sealed class, it would be possible for a type to exist which would inherit from List<AMQuestion>
and implement IList<IQuestion>
. 因为
List<T>
不是密封类,所以可能存在一个类型,它将继承自List<AMQuestion>
并实现IList<IQuestion>
。 Unless you implement such a type yourself, it's extremely unlikely that one will ever actually exist. 除非你自己实现这样一种类型,否则一个人实际存在的可能性极小。 Nonetheless, it would be perfectly legitimate to say, eg
尽管如此,例如说,这是完全合法的
class SillyList : List<AMQuestion>, IList<IQuestion> { ... }
and explicitly implement all the type-specific members of IList<IQuestion>
. 并显式实现
IList<IQuestion>
所有特定于类型的成员。 It would thus also be perfectly legitimate to say "If this variable holds a reference to an instance of a type derived from List<AMQuestion>
, and if that instance's type also implements IList<IQuestion>
, convert the reference to the latter type. 因此,如果“如果此变量包含对从
List<AMQuestion>
派生的类型的实例的引用,并且该实例的类型也实现IList<IQuestion>
,则将引用转换为后一种类型也是完全合法的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.