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

时间:2021-06-29 21:03:59

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 变为IEnumerable 。 out意味着T只能从水果中出来,并且在IEnumerable 上根本没有任何方法将T作为参数,所以你永远不能错误地将Banana传递给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 视为List ,而不是。另一个问题是,每个PackageCall实例实际上都是AbstractPackageCall。

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 不从List 继承。您可以使用Cast <>()扩展方法,如:

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 可以包含从AbstractPackageCall派生的任何内容,而List 则不能 - 它只能包含从PackageCall派生的内容。

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 中 - 但AnotherPackageCall不是从PackageCall派生的!这就是为什么不允许这种转换的原因。

#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 变为IEnumerable 。 out意味着T只能从水果中出来,并且在IEnumerable 上根本没有任何方法将T作为参数,所以你永远不能错误地将Banana传递给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 视为List ,而不是。另一个问题是,每个PackageCall实例实际上都是AbstractPackageCall。

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 不从List 继承。您可以使用Cast <>()扩展方法,如:

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 可以包含从AbstractPackageCall派生的任何内容,而List 则不能 - 它只能包含从PackageCall派生的内容。

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 中 - 但AnotherPackageCall不是从PackageCall派生的!这就是为什么不允许这种转换的原因。