【问题标题】:Casting List<> of Derived class to List<> of base class将派生类的 List<> 转换为基类的 List<>
【发布时间】:2010-09-15 18:47:23
【问题描述】:

我有两个类:一个基类(动物)和一个派生自 it (Cat).Base 类包含一个以 List 作为输入参数的虚拟方法 Play。类似这样的东西

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication9
{
    class Animal
    {
        public virtual void Play(List<Animal> animal) { }
    }
    class Cat : Animal
    {
        public override void Play(List<Animal> animal)
        {
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();
            cat.Play(new List<Cat>());
        }
    }
}

当我编译上述程序时,我得到以下错误

错误 2 参数 1:无法从 'System.Collections.Generic.List' 转换为 'System.Collections.Generic.List'

有没有办法做到这一点?

【问题讨论】:

标签: c# generics


【解决方案1】:

您不能这样做的原因是列表是可写的。假设它是合法的,然后看看出了什么问题:

List<Cat> cats = new List<Cat>();
List<Animal> animals = cats; // Trouble brewing...
animals.Add(new Dog()); // hey, we just added a dog to a list of cats...
cats[0].Speak(); // Woof!

好吧,狗我的猫,那是坏事。

您想要的功能称为“通用协方差”,C# 4 支持已知安全的接口。 IEnumerable&lt;T&gt; 没有办法写入序列,所以是安全的。

class Animal    
{    
    public virtual void Play(IEnumerable<Animal> animals) { }    
}    
class Cat : Animal    
{    
    public override void Play(IEnumerable<Animal> animals) { }    
}    
class Program    
{    
    static void Main()    
    {    
        Cat cat = new Cat();    
        cat.Play(new List<Cat>());    
    }    
}  

这将在 C# 4 中工作,因为 List&lt;Cat&gt; 可转换为 IEnumerable&lt;Cat&gt;,而 IEnumerable&lt;Cat&gt; 可转换为 IEnumerable&lt;Animal&gt;。 Play 无法使用 IEnumerable&lt;Animal&gt; 将狗添加到实际上是猫列表的内容中。

【讨论】:

  • 我错过了让 Play 成为 Animal 的成员如何增加解释。也就是说,Play 采用 IEnumerable。 IMO 如果它只是 Play() 并且使用赋值将示例放置在 void Main() 中会更清楚。
  • @Dykam:它没有。我试图遵循原始海报中列出的代码结构。
  • 啊,我明白了。由于某种原因,我没有链接两个 sn-ps。
  • 加一个保护我的猫安全
  • 这是我完全理解协方差的第一个解释。 “因为列表是可写的” - 这是关键。我认为大多数情况下人们需要这样做List&lt;IFoo&gt; = new List&lt;FooImplementer&gt;(); 他们不明白为什么它不起作用 - 他们使用接口......他们可能不知道 1. 协变接口必须是容器,而不是类型参数,2. IReadOnlyList 的存在,例如 - 在我的情况下......
【解决方案2】:

你可以做一些事情。一个例子是将列表的 elements 转换为 Animal

使用您的代码:

cat.Play(new List<Cat>().Cast<Animal>().ToList());

另一个是使Animal 通用,所以cat.Play(new List&lt;Cat&gt;()); 可以工作。

class Animal<T>
{
    public virtual void Play(List<T> animals) { }
}
class Cat : Animal<Cat>
{
    public override void Play(List<Cat> cats)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        Cat cat = new Cat();
        cat.Play(new List<Cat>());
    }
}

另一种方法是不使 Animal 通用,而是使 Play 方法并将其限制为 T : Animal

class Animal
{
    public virtual void Play<T>(List<T> animals) where T : Animal { }
}
class Cat : Animal
{
    public override void Play<T>(List<T> animals) 
    {
    }
}

最后,如果您使用 C# 4 并且只需要枚举列表而不修改它,请查看 Eric Lippert 在IEnumerable&lt;Animal&gt; 上的回答。

【讨论】:

  • 另外 +1 指出您可能应该使用 IEnumerable 作为参数类型而不是 List
  • 我不明白最后一点;为什么 Cat 类有一个带有名为 Cat 的类型参数的方法?在同一个声明中拥有两个具有相同名称的类型,这不是非常令人困惑吗?
  • @Eric,我只是傻了。
  • 嗯,差不多就解决了。我正在成为一名水管工。
  • 水管工赚大钱,水管不可能外包给海外公司。不过,我更喜欢用电而不是水。如果我厌倦了编译器管道工作,也许我会这样做。
【解决方案3】:

您正在寻找通用集合协方差。但显然,您使用的 C# 版本不支持该功能。

您可以使用Cast&lt;T&gt;() 扩展方法来解决它。但请注意,这将创建原始列表的副本,而不是将原始列表作为不同类型传递:

cat.Play((new List<Cat>()).Cast<Animal>().ToList());

【讨论】:

  • List 根本不支持,因为它是 in&out 泛型。
  • @Dykam - 你能详细说明一下吗?评论没有多大意义。
  • 嗯,由于 List 既接受输入,又返回输出,协变和逆变是不可能的。如果你能做到List&lt;Animal&gt; list = new List&lt;Dog&gt;(); 下面会导致应用程序崩溃:list.Add(new Elephant());
【解决方案4】:

使用扩展方法 Cast()

所以:

class Program
{
    static void Main(string[] args)
    {
        Cat cat = new Cat();
        cat.Play(new List<Cat>().Cast<Animal>());
    }
}

原因是 b/c .net 3.5 不支持协方差,但 4.0 支持:)

【讨论】:

  • 原因已关闭。 cat.Play(new List&lt;Cat&gt;()); 也无法在 4 中使用现有代码。
【解决方案5】:

每个人都已经提到了 cast 方法。如果你不能更新到 4.0 隐藏演员表的方法是

class Cat : Animal
{
    public override void Play(List<Animal> animal)
    {
         Play((List<Cat>)animal);
    }
    public virtual void Play(List<Cat> animal)
    {
    }
}

这是 IEnumableIEnumarable&lt;T&gt; GetEnumerator 的相同技巧

【讨论】:

    猜你喜欢
    • 2012-05-19
    • 2014-01-22
    • 1970-01-01
    • 2017-01-08
    • 2013-11-17
    • 2020-08-30
    • 1970-01-01
    相关资源
    最近更新 更多