我正在尝试将DerivedClass
的列表传递给一个带有BaseClass
列表的函数,但是我收到了错误:
cannot convert from
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>'
to
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>'
现在我可以将List<DerivedClass>
投射到List<BaseClass>
,但除非我理解为什么编译器不允许这样做,否则我感到不自在。
我发现的解释只是说它以某种方式违反了类型安全,但我没有看到它。任何人都可以帮助我吗?
编译器允许从List<DerivedClass>
转换为List<BaseClass>
的风险是什么?
这是我的SSCCE:
class Program
{
public static void Main()
{
BaseClass bc = new DerivedClass(); // works fine
List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error
doSomething(new List<DerivedClass>()); // this line has an error
}
public void doSomething(List<BaseClass> bc)
{
// do something with bc
}
}
class BaseClass
{
}
class DerivedClass : BaseClass
{
}
答案 0 :(得分:45)
这是因为List<T>
是in-variant
,而不是co-variant
,因此您应该更改为支持IEnumerable<T>
的{{1}},它应该有效:
co-variant
的信息
答案 1 :(得分:43)
我发现的解释只是说它以某种方式违反了类型安全,但我没有看到它。编译器允许从
List<DerivedClass>
转换为List<BaseClass>
的风险是什么?
几乎每天都会问这个问题。
List<Mammal>
无法转换为List<Animal>
因为您可以将蜥蜴放入动物列表中。 List<Mammal
&gt;无法转换为List<Giraffe>
,因为列表中可能已有老虎。
因此,List<T>
必须在T中不变。
但是,List<Mammal>
可以转换为IEnumerable<Animal>
(从C#4.0开始),因为IEnumerable<Animal>
上没有添加蜥蜴的方法。 IEnumerable<T>
在T.
答案 2 :(得分:12)
您所描述的行为称为协方差 - 如果A
B
,那么List<A>
是 List<B>
。
但是,对于像List<T>
这样的可变类型,这基本上是不安全的。
如果可行,该方法可以将new OtherDerivedClass()
添加到实际上只能保留DerivedClass
的列表。
协方差在不可变类型上是安全的,尽管.Net仅在接口和委托中支持它
如果您将List<T>
参数更改为IEnumerable<T>
,则可以使用
答案 3 :(得分:2)
如果有一个派生自基类的类,则不会自动派生这些类的任何容器。因此,您不能只将List<Derived>
投射到List<Base>
。
使用.Cast<T>()
创建一个新列表,其中每个对象都被强制转换回基类:
List<MyDerived> list1 = new List<MyDerived>();
List<MyBase> list2 = list1.Cast<MyBase>().ToList();
请注意,这是一个新列表,而不是原始列表的演员版本,因此对此新列表的操作不会反映在原始列表中。但是,对所包含对象的操作将反映出来。
答案 4 :(得分:1)
如果你可以写
List<BaseClass> bcl = new List<DerivedClass>();
你可以打电话
var instance = new AnotherClassInheritingFromBaseClass();
bc1.Add(instance);
将不是DerivedClass的实例添加到列表中。
答案 5 :(得分:0)
我使用的解决方案是创建扩展类:
public static class myExtensionClass
{
public void doSomething<T>(List<BaseClass> bc) where T: BaseClass
{
// do something with bc
}
}
它使用泛型,但是当你打电话给它时,你不必指定课程,因为你已经告诉过#c;编译器的类型与扩展类相同。
你会这样称呼它:
List<DerivedClass> lst = new List<DerivedClass>();
lst.doSomething();