为什么我不能将List 分配给List ?

我定义了以下类:

public abstract class AbstractPackageCall { ... } 

我还定义了这个类的子类:

 class PackageCall : AbstractPackageCall { ... } 

AbstractPackageCall还有其他几个子节点

现在我想进行以下调用:

  List calls = package.getCalls(); 

但我总是得到这个例外:

 Error 13 Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.List' 

这里有什么问题? 这是Package#getCalls的方法

  internal List getCalls() { return calls; } 

理解为什么不允许这样做的最简单方法是以下示例:

 abstract class Fruit { } class Apple : Fruit { } class Banana : Fruit { } // This should intuitively compile right? Cause an Apple is Fruit. List fruits = new List(); // But what if I do this? Adding a Banana to a list of Apples fruits.Add(new Banana()); 

最后一句话会破坏.NET的类型安全性。

但是,数组允许这样做:

 Fruit[] fruits = new Apple[10]; // This is perfectly fine 

然而,将Banana放入fruits仍会破坏类型安全性,因此.NET必须对每个数组插入进行类型检查,如果它实际上不是Apple ,则抛出exception。 这可能是一个(小)性能损失,但这可以通过在任一类型周围创建struct包装器来规避,因为这种检查不会发生在值类型中(因为它们不能从任何东西inheritance)。 起初,我不明白为什么做出这个决定,但你会经常遇到为什么这个有用。 最常见的是String.Format ,它接受params object[]并且任何数组都可以传递给它。

但是在.NET 4中,存在类型安全协方差/逆变,它允许您进行类似这样的分配,但前提是它们可certificate是安全的。 什么是可靠的安全?

 IEnumerable fruits = new List(); 

以上工作在.NET 4中,因为IEnumerable变为IEnumerableout意味着T只能 fruits 出来 ,并且在IEnumerable根本没有任何方法T作为参数,所以你永远不能错误地将Banana传递给IEnumerable

逆差异大致相同,但我总是忘记它的具体细节。 不出所料,为此现在在类型参数上有in关键字。

现在,如果您想进行以下调用:

 List calls = package.getCalls(); // select only AbstractPackageCall items List calls = calls.Select(); calls.Add(new AnotherPackageCall()); 

我称之为概括。

此外,您可以使用更具体的:

 List calls = package.getCalls(); // select only AnotherPackageCall items List calls = calls.Select(); calls.Add(new AnotherPackageCall()); 

使用扩展方法实现此方法:

 public static class AbstractPackageCallHelper { public static List Select(this List source) where T : AbstractPackageCall where U : T { List target = new List(); foreach(var element in source) { if (element is U) { target.Add((U)element); } } return target; } } 

你为什么尝试不起作用

您要求编译器将List视为List ,而不是。 另一个问题是,每个PackageCall实例实际上都是AbstractPackageCall

什么会起作用

 var calls = package.getCalls().Cast().ToList(); 

为什么你的尝试永远不会被允许工作

即使package.getCalls()的返回值内的每个项都派生自AbstractPackageCall ,我们也不能将整个列表视为List 。 如果我们能够:

 var calls = package.getCalls(); // calls is List List apcs = calls; // ILLEGAL, but assume we could do it apcs.Add(SomeOtherConcretePackageCall()); // BOOM! 

如果我们可以这样做,那么我们可以将SomeOtherConcretePackageCall添加到List

因为List不从Listinheritance。 您可以使用Cast<>()扩展方法,如:

 var calls = package.getCalls().Cast(); 

您无法执行此转换,因为List可以包含从AbstractPackageCall派生的任何AbstractPackageCall ,而List则不能 – 它只能包含从PackageCall派生的内容。

考虑是否允许此演员:

 class AnotherPackageCall : AbstractPackageCall { } // ... List calls = package.getCalls(); calls.Add(new AnotherPackageCall()); 

您刚刚将AnotherPackageCall添加到List – 但AnotherPackageCall不是从AnotherPackageCall派生的! 这就是为什么不允许这种转换的原因。