I defined the following class:
我定义了以下类:
public abstract class AbstractPackageCall
{
...
}
I also define a subclass of this class:
我还定义了这个类的子类:
class PackageCall : AbstractPackageCall
{
...
}
There are also several other subclases of AbstractPackageCall
AbstractPackageCall还有其他几个子节点
Now I want to make the following call:
现在我想进行以下调用:
List<AbstractPackageCall> calls = package.getCalls();
But I always get this exception:
但我总是得到这个例外:
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>'
What is the problem here? This is the method Package#getCalls
这里有什么问题?这是Package#getCalls的方法
internal List<PackageCall> getCalls()
{
return calls;
}
5 个解决方案
#1
54
The simplest way to understand why this is not allowed is the following example:
理解为什么不允许这样做的最简单方法是以下示例:
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());
The last statement would ruin the type safety of .NET.
最后一句话会破坏.NET的类型安全性。
Arrays however, do allow this:
但是,数组允许这样做:
Fruit[] fruits = new Apple[10]; // This is perfectly fine
However, putting a Banana
into fruits
would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple
. This is potentially a (small) performance hit, but this can be circumvented by creating a struct
wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format
, which takes params object[]
and any array can be passed into this.
然而,将香蕉放入水果仍会破坏类型安全性,因此.NET必须对每个数组插入进行类型检查,如果它实际上不是Apple,则抛出异常。这可能是一个(小)性能损失,但这可以通过在任一类型周围创建结构包装器来规避,因为这种检查不会发生在值类型中(因为它们不能从任何东西继承)。起初,我不明白为什么做出这个决定,但你会经常遇到为什么这个有用。最常见的是String.Format,它接受params object []并且任何数组都可以传递给它。
In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?
但是在.NET 4中,存在类型安全协方差/逆变,它允许您进行类似这样的分配,但前提是它们可证明是安全的。什么是可靠的安全?
IEnumerable<Fruit> fruits = new List<Apple>();
The above works in .NET 4, because IEnumerable<T>
became IEnumerable<out T>
. The out
means that T
can only ever come out of fruits
and that there's no method at all on IEnumerable<out T>
that ever takes T
as a parameter, so you can never incorrectly pass a Banana
into IEnumerable<Fruit>
.
以上工作在.NET 4中,因为IEnumerable
Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in
keyword on type parameters.
逆差异大致相同,但我总是忘记它的具体细节。不出所料,为此现在在类型参数上有in关键字。
#2
2
Now if you want to make the following call:
现在,如果您想进行以下调用:
List<PackageCall> calls = package.getCalls();
// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
I call this generalization.
我称之为概括。
Also, you can use this more specific:
此外,您可以使用更具体的:
List<PackageCall> calls = package.getCalls();
// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
Implement this using extension method:
使用扩展方法实现此方法:
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
2
Why what you try does't work
You are asking the compiler to treat a List<PackageCall>
as a List<AbstractPackageCall>
, which it is not. It's another matter that each of those PackageCall
instances is in fact an AbstractPackageCall
.
您要求编译器将List
What would work instead
var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();
Why what you try will never be allowed to work
Even though each item inside the return value of package.getCalls()
derives from AbstractPackageCall
, we can't treat the whole list as List<AbstractPackageCall>
. Here's what would happen if we could:
即使package.getCalls()的返回值内的每个项都派生自AbstractPackageCall,我们也不能将整个列表视为List
var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> apcs = calls; // ILLEGAL, but assume we could do it
apcs.Add(SomeOtherConcretePackageCall()); // BOOM!
If we could do that, then we could add a SomeOtherConcretePackageCall
to a List<PackageCall>
.
如果我们可以这样做,那么我们可以将SomeOtherConcretePackageCall添加到List
#4
1
Because List<PackageCall>
does not inherit from List<AbstractPackageCall>
. You can use the Cast<>()
extension method, like:
因为List
var calls = package.getCalls().Cast<AbstractPackageCall>();
#5
0
You cannot perform this conversion, because a List<AbstractPackageCall>
can contain anything that derives from AbstractPackageCall
, while a List<PackageCall>
cannot -- it can only contain things that derive from PackageCall
.
您无法执行此转换,因为List
Consider if this cast were allowed:
考虑是否允许此演员:
class AnotherPackageCall : AbstractPackageCall { }
// ...
List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());
You just added an AnotherPackageCall
into a List<PackageCall>
-- but AnotherPackageCall
does not derive from PackageCall
! That's why this conversion is not allowed.
您刚刚将AnotherPackageCall添加到List
#1
54
The simplest way to understand why this is not allowed is the following example:
理解为什么不允许这样做的最简单方法是以下示例:
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());
The last statement would ruin the type safety of .NET.
最后一句话会破坏.NET的类型安全性。
Arrays however, do allow this:
但是,数组允许这样做:
Fruit[] fruits = new Apple[10]; // This is perfectly fine
However, putting a Banana
into fruits
would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple
. This is potentially a (small) performance hit, but this can be circumvented by creating a struct
wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format
, which takes params object[]
and any array can be passed into this.
然而,将香蕉放入水果仍会破坏类型安全性,因此.NET必须对每个数组插入进行类型检查,如果它实际上不是Apple,则抛出异常。这可能是一个(小)性能损失,但这可以通过在任一类型周围创建结构包装器来规避,因为这种检查不会发生在值类型中(因为它们不能从任何东西继承)。起初,我不明白为什么做出这个决定,但你会经常遇到为什么这个有用。最常见的是String.Format,它接受params object []并且任何数组都可以传递给它。
In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?
但是在.NET 4中,存在类型安全协方差/逆变,它允许您进行类似这样的分配,但前提是它们可证明是安全的。什么是可靠的安全?
IEnumerable<Fruit> fruits = new List<Apple>();
The above works in .NET 4, because IEnumerable<T>
became IEnumerable<out T>
. The out
means that T
can only ever come out of fruits
and that there's no method at all on IEnumerable<out T>
that ever takes T
as a parameter, so you can never incorrectly pass a Banana
into IEnumerable<Fruit>
.
以上工作在.NET 4中,因为IEnumerable
Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in
keyword on type parameters.
逆差异大致相同,但我总是忘记它的具体细节。不出所料,为此现在在类型参数上有in关键字。
#2
2
Now if you want to make the following call:
现在,如果您想进行以下调用:
List<PackageCall> calls = package.getCalls();
// select only AbstractPackageCall items
List<AbstractPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
I call this generalization.
我称之为概括。
Also, you can use this more specific:
此外,您可以使用更具体的:
List<PackageCall> calls = package.getCalls();
// select only AnotherPackageCall items
List<AnotherPackageCall> calls = calls.Select();
calls.Add(new AnotherPackageCall());
Implement this using extension method:
使用扩展方法实现此方法:
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
2
Why what you try does't work
You are asking the compiler to treat a List<PackageCall>
as a List<AbstractPackageCall>
, which it is not. It's another matter that each of those PackageCall
instances is in fact an AbstractPackageCall
.
您要求编译器将List
What would work instead
var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();
Why what you try will never be allowed to work
Even though each item inside the return value of package.getCalls()
derives from AbstractPackageCall
, we can't treat the whole list as List<AbstractPackageCall>
. Here's what would happen if we could:
即使package.getCalls()的返回值内的每个项都派生自AbstractPackageCall,我们也不能将整个列表视为List
var calls = package.getCalls(); // calls is List<PackageCall>
List<AbstractPackageCalls> apcs = calls; // ILLEGAL, but assume we could do it
apcs.Add(SomeOtherConcretePackageCall()); // BOOM!
If we could do that, then we could add a SomeOtherConcretePackageCall
to a List<PackageCall>
.
如果我们可以这样做,那么我们可以将SomeOtherConcretePackageCall添加到List
#4
1
Because List<PackageCall>
does not inherit from List<AbstractPackageCall>
. You can use the Cast<>()
extension method, like:
因为List
var calls = package.getCalls().Cast<AbstractPackageCall>();
#5
0
You cannot perform this conversion, because a List<AbstractPackageCall>
can contain anything that derives from AbstractPackageCall
, while a List<PackageCall>
cannot -- it can only contain things that derive from PackageCall
.
您无法执行此转换,因为List
Consider if this cast were allowed:
考虑是否允许此演员:
class AnotherPackageCall : AbstractPackageCall { }
// ...
List<AbstractPackageCall> calls = package.getCalls();
calls.Add(new AnotherPackageCall());
You just added an AnotherPackageCall
into a List<PackageCall>
-- but AnotherPackageCall
does not derive from PackageCall
! That's why this conversion is not allowed.
您刚刚将AnotherPackageCall添加到List