我定义了以下类:
public abstract class AbstractPackageCall
{
...
}
我还定义了这个类的子类:
class PackageCall : AbstractPackageCall
{
...
}
AbstractPackageCall
还有其他几个子类
现在我要拨打以下电话:
List<AbstractPackageCall> calls = package.getCalls();
但我总是遇到这个例外:
Error 13 Cannot implicitly convert type 'System.Collections.Generic.List<Prototype_Concept_2.model.PackageCall>' to 'System.Collections.Generic.List<Prototype_Concept_2.model.AbstractPackageCall>'
这里有什么问题吗?这是方法 Package#getCalls
internal List<PackageCall> getCalls()
{
return calls;
}
了解为什么不允许这样做的最简单方法是以下示例:
abstract class Fruit
{
}
class Apple : Fruit
{
}
class Banana : Fruit
{
}
// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();
// 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
,则抛出异常。这可能会对性能造成(小)影响,但是可以通过围绕任一类型创建 struct
包装器来避免这种情况,因为值类型不会发生此检查(因为它们不能从任何类型继承)。起初,我不明白为什么做出这个决定,但你会经常遇到为什么这很有用。最常见的是 String.Format
,它接受 params object[]
并且任何数组都可以传递到其中。
但在 .NET 4 中,存在类型安全的协变/逆变,它允许您进行一些类似的分配,但前提是它们被证明是安全的。什么是可证明安全的?
IEnumerable<Fruit> fruits = new List<Apple>();
上面的代码适用于 .NET 4,因为
IEnumerable<T>
变成了 IEnumerable<out T>
。 out
意味着 T
只能来自 fruits
的 out并且
IEnumerable<out T>
上根本没有 任何将
T
作为参数的方法,所以你永远不会错误地传递一个
Banana
进入IEnumerable<Fruit>
。逆变大致相同,但我总是忘记它的具体细节。毫不奇怪,现在类型参数上有
in
关键字。
List<PackageCall> calls = package.getCalls();
// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
我称之为概括。
此外,您可以使用更具体的:
List<PackageCall> calls = package.getCalls();
// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
使用扩展方法实现此功能:
public static class AbstractPackageCallHelper
{
public static List<U> Select(this List<T> source)
where T : AbstractPackageCall
where U : T
{
List<U> target = new List<U>();
foreach(var element in source)
{
if (element is U)
{
target.Add((U)element);
}
}
return target;
}
}
List<PackageCall>
视为
List<AbstractPackageCall>
,但事实并非如此。另一个问题是,每个
PackageCall
实例实际上都是
AbstractPackageCall
。什么会起作用
var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();
为什么你的尝试永远不会成功
package.getCalls()
返回值中的每一项都源自
AbstractPackageCall
,但我们不能将整个列表视为
List<AbstractPackageCall>
。如果我们可以的话,将会发生以下情况:
var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> apcs = calls; // ILLEGAL, but assume we could do it
apcs.Add(SomeOtherConcretePackageCall()); // BOOM!
如果我们能做到这一点,那么我们可以将
SomeOtherConcretePackageCall
添加到
List<PackageCall>
。
List<AbstractPackageCall>
可以包含从
AbstractPackageCall
派生的任何内容,而
List<PackageCall>
不能——它只能包含从
PackageCall
派生的内容。考虑是否允许这种演员阵容:
class AnotherPackageCall : AbstractPackageCall { }
// ...
List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());
您刚刚将
AnotherPackageCall
添加到
List<PackageCall>
中 - 但
AnotherPackageCall
并非源自
PackageCall
! 这就是为什么不允许这种转换。
List<PackageCall>
不继承自
List<AbstractPackageCall>
。 您可以使用
Cast<>()
扩展方法,例如:
var calls = package.getCalls().Cast<AbstractPackageCall>();
public abstract class Foo {}
public class Bar1: Foo {}
public class Bar2: Foo {}
public void doSomethingWithFoos(List<Foo> foos) {
// ...
}
进入这个:
public abstract class Foo {}
public class Bar1: Foo {}
public class Bar2: Foo {}
public void doSomethingWithFoos<T>(List<T> foos) where T: Foo {
// ...
}