简体   繁体   English

与C#Generics的协方差

[英]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: 如果有人可以解释两件事:

  • Why this doesn't work. 为什么这不起作用。
  • How I can achieve the desired effect. 我怎样才能达到预期的效果。

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.AsReadOnlyIReadOnlyList<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 : 在这种情况下显式转换意味着将AMQuestionIQuestion

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM