【问题标题】:Why can't I assign a List<Derived> to a List<Base>?为什么我不能将 List<Derived> 分配给 List<Base>?
【发布时间】:2011-01-10 23:52:38
【问题描述】:

我定义了以下类:

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>' 

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

 internal List<PackageCall> getCalls()
        {
            return calls;
        }

【问题讨论】:

标签: c# .net generics covariance


【解决方案1】:

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

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&lt;T&gt; 变成了 IEnumerable&lt;out T&gt;out 意味着T 只能fruits 中出来,并且在IEnumerable&lt;out T&gt;根本没有方法可以将T 视为一个参数,所以你永远不会错误地将Banana 传入 IEnumerable&lt;Fruit&gt;

逆变器大致相同,但我总是忘记它的确切细节。不出所料,现在类型参数上有 in 关键字。

【讨论】:

  • 为什么投反对票?如果答案不是愚蠢的、有争议的或冒犯性的,请随时发表评论,以便我可以修复/纠正问题。
  • 这是一个很好的答案。公平对待你花时间
【解决方案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】:

为什么你尝试的方法不起作用

您要求编译器将List&lt;PackageCall&gt; 视为List&lt;AbstractPackageCall&gt;,但事实并非如此。每个PackageCall 实例实际上都是AbstractPackageCall 是另一回事。

什么会起作用

var calls = package.getCalls().Cast<AbstractPackageCall>().ToList();

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

即使package.getCalls() 的返回值中的每一项都派生自AbstractPackageCall,我们也不能将整个列表视为List&lt;AbstractPackageCall&gt;。如果可以的话,会发生以下情况:

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&lt;PackageCall&gt;

【讨论】:

    【解决方案4】:

    因为List&lt;PackageCall&gt; 不继承自List&lt;AbstractPackageCall&gt;。您可以使用Cast&lt;&gt;() 扩展方法,例如:

    var calls = package.getCalls().Cast<AbstractPackageCall>();
    

    【讨论】:

    • 错误 3 无法将类型“System.Collections.Generic.IEnumerable”隐式转换为“System.Collections.Generic.List”。存在显式转换(您是否缺少演员表?}
    • 是的,刚刚修正了我的回复。
    【解决方案5】:

    您无法执行此转换,因为 List&lt;AbstractPackageCall&gt; 可以包含派生自 AbstractPackageCall 的任何内容,而 List&lt;PackageCall&gt; 不能 - 它只能包含派生自 PackageCall 的内容。

    考虑是否允许这种演员:

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

    您刚刚将AnotherPackageCall 添加到List&lt;PackageCall&gt; 中——但AnotherPackageCall 并非源自PackageCall!这就是不允许这种转换的原因。

    【讨论】:

    • 数组允许这种转换,这可能是他问这个的原因。
    猜你喜欢
    • 2018-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-25
    • 1970-01-01
    相关资源
    最近更新 更多