我定义了以下类:
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;
}
答案 0 :(得分:54)
理解为什么不允许这样做的最简单方法是以下示例:
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必须对每个数组插入进行类型检查,如果它实际上不是{{1}则抛出异常}}。这可能是一个(小)性能损失,但这可以通过在任一类型周围创建Apple
包装器来规避,因为这种检查不会发生在值类型中(因为它们不能从任何东西继承)。起初,我不明白为什么做出这个决定,但你会经常遇到为什么这个有用。最常见的是struct
,它取String.Format
并且任何数组都可以传递给它。
在.NET 4中,有类型安全的协方差/逆变,它允许你做出一些像这样的任务,但前提是它们是可证明安全的。什么是可靠的安全?
params object[]
以上在.NET 4中有效,因为IEnumerable<Fruit> fruits = new List<Apple>();
变为IEnumerable<T>
。 IEnumerable<out T>
表示out
T
只能 fruits
,而IEnumerable<out T>
上的根本没有方法 } T
作为参数,因此您永远不能错误地将Banana
传递到 IEnumerable<Fruit>
。
逆变法大致相同,但我总是忘记它的具体细节。不出所料,为此,类型参数现在有in
个关键字。
答案 1 :(得分:2)
您要求编译器将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>
添加到{{1}}。
答案 2 :(得分:2)
现在,如果您想进行以下呼叫:
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;
}
}
答案 3 :(得分:1)
因为List<PackageCall>
不会从List<AbstractPackageCall>
继承。您可以使用Cast<>()
扩展名方法,例如:
var calls = package.getCalls().Cast<AbstractPackageCall>();
答案 4 :(得分:0)
您无法执行此转换,因为List<AbstractPackageCall>
可以包含从AbstractPackageCall
派生的任何内容,而List<PackageCall>
则不能 - 它只能包含从{{1}派生的内容}。
考虑是否允许此演员表:
PackageCall
您刚刚将class AnotherPackageCall : AbstractPackageCall { }
// ...
List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());
添加到AnotherPackageCall
- 但List<PackageCall>
并非来自AnotherPackageCall
!这就是为什么不允许这种转换。