给定一个接口IQuestion
和该接口AMQuestion
的实现,假设如下示例:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed;
如预期的那样,这个例子产生一个编译错误,说这两个不是同一类型。但它表明存在显式转换。所以我把它改成这样:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
然后编译,但在运行时,nonTyped
始终为空。如果有人能解释两件事:
- 为什么不工作
- 如何达到我想要的效果
非常感谢。谢谢你!
AMQuestion
实现IQuestion
接口的事实并不转化为List<IQuestion>
派生的List<AMQuestion>
。
因为此强制转换是非法的,所以您的as
操作符返回null
。
必须将每个项单独强制转换为:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
关于您的评论,请考虑以下代码,其中包含常见的动物示例:
//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!
如果允许将List<Lizard>
强制转换为List<Animal>
,那么理论上可以在该列表中添加新的Donkey
,这将破坏继承。
为什么不起作用:如果值的动态类型不能转换为目标类型,则as
返回null
,而List<AMQuestion>
不能转换为IList<IQuestion>
。
但是为什么不能呢?好吧,检查一下:
List<AMQuestion> typed = new List<AMQuestion>();
IList<IQuestion> nonTyped = typed as IList<IQuestion>;
nonTyped.Add(new OTQuestion());
AMQuestion whaaaat = typed[0];
IList<IQuestion>
说"你可以添加任何IQuestion
给我"。但如果它是List<AMQuestion>
的话,这是一个无法兑现的承诺。
现在,如果您不想添加任何东西,只需将其视为IQuestion
兼容的东西的集合,那么最好的方法就是将其与List.AsReadOnly
强制转换为IReadOnlyList<IQuestion>
。由于只读列表不能添加奇怪的内容,因此可以正确地对其进行强制类型转换。
问题是List<AMQuestion>
不能被强制转换为IList<IQuestion>
,所以使用as
运算符没有帮助。在这种情况下,显式转换意味着将AMQuestion
转换为IQuestion
:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>.ToList();
顺便说一下,你的标题中有"协方差"这个词。在IList
中,类型是而不是协变。这就是演员阵容不存在的原因。原因是IList
接口在某些参数中有T
,在某些返回值中有,所以T
既不能用in
也不能用out
。(@Sneftel有一个很好的例子来说明为什么不允许这种类型。)
如果你只需要从列表中读取,你可以使用IEnumerable
代替:
IEnumerable<IQuestion> = typed;
这将工作,因为IEnumerable<out T>
已经定义了out
,因为你不能传递T
作为参数。你通常应该在你的代码中做出最弱的"承诺",以保持它的可扩展性。
IList<T>
对T
不协变;这是不可能的,因为接口在"输入"位置定义了接受T
类型值的函数。然而,IEnumerable<T>
是T
的协变。如果您可以限制您的类型为IEnumerable<T>
,您可以这样做:
List<AMQuestion> typed = new List<AMQuestion>();
IEnumerable<IQuestion> nonTyped = typed;
这不会对列表进行任何转换。
您不能将List<AMQuestion>
转换为List<IQuestion>
的原因(假设AMQuestion实现了该接口)是,必须对List<T>.Add
等函数进行多次运行时检查,以确保您确实添加了AMQuestion
。
如果不存在有效的强制类型转换,则"as"操作符将始终返回null -这是定义的行为。您必须像这样转换或强制转换列表:
IList<IQuestion> nonTyped = typed.Cast<IQuestion>().ToList();
具有泛型类型参数的类型只能是协变的,如果该泛型类型只出现在读访问中,则只能是逆变的,如果它只出现在写访问中。IList<T>
允许对T
类型的值进行读和写访问,所以它不能是变体!
假设允许将List<AMQuestion>
赋值给IList<IQuestion>
类型的变量。现在让我们实现一个class XYQuestion : IQuestion
,并将该类型的值插入到IList<IQuestion>
中,这看起来完全合法。这个列表仍然引用List<AMQuestion>
,但是我们不能将XYQuestion
插入List<AMQuestion>
!因此,这两种列表类型不兼容赋值操作。
IList<IQuestion> list = new List<AMQuestion>(); // Not allowed!
list.Add(new XYQuestion()); // Uuups!
因为List<T>
不是一个密封类,所以有可能存在继承List<AMQuestion>
并实现IList<IQuestion>
的类型。除非您自己实现这样的类型,否则实际上不太可能存在这样的类型。尽管如此,完全可以这样说,例如
class SillyList : List<AMQuestion>, IList<IQuestion> { ... }
并显式地实现IList<IQuestion>
的所有特定类型的成员。因此,也可以完全合理地说:"如果这个变量持有对从List<AMQuestion>
派生的类型的实例的引用,并且如果该实例的类型也实现了IList<IQuestion>
,则将引用转换为后一种类型。"