【问题标题】:Cannot convert from List<DerivedClass> to List<BaseClass>无法从 List<DerivedClass> 转换为 List<BaseClass>
【发布时间】:2013-06-02 17:58:29
【问题描述】:

我正在尝试将 DerivedClass 的列表传递给一个接受 BaseClass 列表的函数,但我得到了错误:

cannot convert from 
'System.Collections.Generic.List<ConsoleApplication1.DerivedClass>' 
to 
'System.Collections.Generic.List<ConsoleApplication1.BaseClass>'

现在我可以将我的List&lt;DerivedClass&gt; 转换为List&lt;BaseClass&gt;,但除非我明白为什么编译器不允许这样做,否则我会觉得不自在。

我发现的解释只是说它以某种方式违反了类型安全,但我没有看到它。谁能帮帮我?

编译器允许从List&lt;DerivedClass&gt; 转换为List&lt;BaseClass&gt; 的风险是什么?


这是我的 SSCCE:

class Program
{
    public static void Main()
    {
        BaseClass bc = new DerivedClass(); // works fine
        List<BaseClass> bcl = new List<DerivedClass>(); // this line has an error

        doSomething(new List<DerivedClass>()); // this line has an error
    }

    public void doSomething(List<BaseClass> bc)
    {
        // do something with bc
    }
}

class BaseClass
{
}

class DerivedClass : BaseClass
{
}

【问题讨论】:

  • 类派生自其基类,但List&lt;Derived&gt; 不是派生自List&lt;Base&gt;
  • @TimSchmelter 这是否意味着这种继承可能成为Generic&lt;Derived&gt; 可以被认为是从Generic&lt;Base&gt; 派生的功能?还是这样的功能会导致问题?
  • 只是没有这样实现。 marcgravell.blogspot.fr/2009/02/…
  • @TimSchmelter:不;那样是不可能的。
  • @SamIam:您应该首先将其实例化为 List&lt;Base&gt;

标签: c#


【解决方案1】:

我使用的一个解决方案是创建一个扩展类:

public static class myExtensionClass 
{
    public void doSomething<T>(List<BaseClass> bc) where T: BaseClass
    {
        // do something with bc
    }
}

它使用泛型,但是当您调用它时,您不必指定类,因为您已经“告诉”编译器类型与扩展类相同。

你可以这样称呼它:

List<DerivedClass> lst = new List<DerivedClass>();
lst.doSomething();

【讨论】:

    【解决方案2】:

    我发现的解释只是说它以某种方式违反了类型安全,但我没有看到它。编译器允许从List&lt;DerivedClass&gt; 转换为List&lt;BaseClass&gt; 的风险是什么?

    这个问题几乎每天都会被问到。

    List&lt;Mammal&gt; 无法转换为 List&lt;Animal&gt;,因为您可以将蜥蜴放入动物列表中List&lt;Mammal> 无法转换为 List&lt;Giraffe&gt;,因为列表中可能已经有老虎了

    因此,List&lt;T&gt; 在 T 中必须是不变的

    但是,List&lt;Mammal&gt; 可以转换为 IEnumerable&lt;Animal&gt;(从 C# 4.0 开始),因为在 IEnumerable&lt;Animal&gt; 上没有添加蜥蜴的方法。 IEnumerable&lt;T&gt; 在 T 中是协变的

    【讨论】:

    • +1 一个解释一切的简单动物学解释。 (IEnumerable 上没有添加蜥蜴的方法 --> 我会检查以防万一;))
    • 我觉得自己很笨……长颈鹿这个看起来很明显,但是为什么你不能把哺乳动物传给可以处理动物的东西,就因为它也可以处理非哺乳动物呢?跨度>
    • @MGOwen:你有一份哺乳动物名单。您决定将其视为动物列表。你把蜥蜴放到动物列表中。 但它确实是哺乳动物的列表。现在你有一个包含蜥蜴的哺乳动物列表,这违反了哺乳动物列表中只有哺乳动物的预期。结论是违反类型系统,因此其中一个步骤一定是非法的。这是第一个:您不能将哺乳动物列表视为动物列表。
    • @RyanPergent:这不是一个新列表。这是同一个列表。 List&lt;T&gt; 是一个引用类型;引用转换重新解释引用的含义。它不会改变引用所指的对象。
    • @RyanPergent: mammals.Cast&lt;Animal&gt;().ToList() 将是一个新的List&lt;Animal&gt;,它与mammals 具有相同的内容,但是这种转换需要建立一个列表的副本,因此速度很慢。如果优化器很聪明,那么引用转换实际上是没有时间的,因为引用转换除了如何解释引用之外,没有任何变化
    【解决方案3】:

    这是因为List&lt;T&gt;in-variant,而不是co-variant,所以你应该改成支持co-variantIEnumerable&lt;T&gt;,它应该可以工作:

    IEnumerable<BaseClass> bcl = new List<DerivedClass>();
    public void doSomething(IEnumerable<BaseClass> bc)
    {
        // do something with bc
    }
    

    关于co-variant in generic的信息

    【讨论】:

    • 从.Net 4.5开始,如果方法需要随机访问集合元素,也可以使用IReadOnlyList&lt;T&gt;
    【解决方案4】:

    当您有一个从基类派生的类时,这些类的任何容器都不会自动派生。所以你不能只将List&lt;Derived&gt; 转换为List&lt;Base&gt;

    使用.Cast&lt;T&gt;() 创建一个新列表,其中每个对象都被转换回基类:

    List<MyDerived> list1 = new List<MyDerived>();
    List<MyBase> list2 = list1.Cast<MyBase>().ToList();
    

    请注意,这是一个新列表,而不是原始列表的转换版本,因此对这个新列表的操作不会反映在原始列表上。但是,对所包含对象的操作会反映出来。

    【讨论】:

    • List&lt;MyBase&gt; list2 = new List&lt;MyBase&gt;(list1); 让我觉得比使用 Cast 更干净。
    【解决方案5】:

    如果你会写

    List<BaseClass> bcl = new List<DerivedClass>(); 
    

    你可以打电话

    var instance = new AnotherClassInheritingFromBaseClass();
    bc1.Add(instance);
    

    将不是 DerivedClass 的实例添加到列表中。

    【讨论】:

      【解决方案6】:

      您所描述的行为称为协方差 - 如果A B,那么List&lt;A&gt; @987654324 @。

      但是,对于像 List&lt;T&gt; 这样的可变类型,这从根本上来说是不安全的。

      如果可能的话,该方法将能够将new OtherDerivedClass() 添加到实际上只能包含DerivedClass 的列表中。

      协方差在不可变类型上是安全的,尽管 .Net 仅在接口和委托中支持它。
      如果您将List&lt;T&gt; 参数更改为IEnumerable&lt;T&gt;,那将起作用

      【讨论】:

      • @SamIam:不;您正在尝试传递List&lt;Derived&gt;。传递包含Deriveds 的List&lt;Base&gt; 可以正常工作。
      • 因此,如果我在传递之前将 List&lt;Derived&gt; 转换或转换为 List&lt;Base&gt; 可以吗?
      • @SamIam:不。关键在于将List&lt;Derived&gt; 转换为List&lt;Base&gt; 从根本上讲是不安全的。如果您首先创建一个List&lt;Base&gt;,它将起作用。
      • @SamIam:SLaks 解释得很好:如果你可以List&lt;Derived&gt; 转换为List&lt;Base&gt;,那么理论上你可以向它添加OtherDerived 的实例(因为列表是可变的)。 但它被实例化为List&lt;Derived&gt;,如果您尝试手动将其转换回List&lt;Derived&gt;,您会得到一个List&lt;Derived&gt;,其中包含OtherDerived
      猜你喜欢
      • 2010-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多