C#中对象列表的向下转型

10

如何将对象列表向下转换,以使列表中的每个对象都被向下转换为派生类的对象?

以下是情景:

我有一个基类和继承自它的两个类,其中基类拥有一个基础项目的List

public class BaseClass
{
    public List<BaseItem> items;
    protected BaseClass()
    {
        // some code to build list of items
    }
}
public class DerivedClass : BaseClass
{
    public DerivedClass : base() {}
}
public class AnotherDerivedClass : BaseClass
{
    public AnotherDerivedClass : base() {}
}

public class BaseItem {}
public class DerivedItem : BaseItem {}
public class AnotherDerivedItem : BaseItem {}

这个想法是不需要复制构建项目列表所需的代码。 BaseItem 包含我需要的所有基本东西,并且我始终可以将 BaseItem 向下转换为派生项目之一。

问题出现在我有一个它们的列表时。因为所有派生类都必须拥有它,所以 BaseClass 中声明了 BaseItem List 。但是,在运行时访问它时,我似乎无法向下转换为派生类。


你可能应该研究一下接口。 - Brad Christie
或者普通的多态性:在基类中使用虚方法,在派生类中进行重写。由于使用基类类型调用重写方法,因此无需进行向下转换。 - Polyfun
4个回答

11
使用LINQ:
    var baseList = new List<BaseClass>();
    var derivedList = baseList.Cast<DerivedClass>();

注意:通常需要进行向下转换(downcast)是一种“坏味道”(smell),表明继承层次结构存在问题,或者实现不正确。有一个基类的想法是,您可以将所有子类视为超类,而无需将其向下转换为各个子类类型。

您可以使用 OfType 而不是 Cast 从超类集合中“挑选出”某些派生类。但是再次强调,没有必要这样做。

请问自己,为什么需要拥有子类-也许需要将一些功能移动到基类中?


这个确切地做到了我所要求的。但是其他答案和评论让我想知道是否可以完全避免这种事情。 - Farinha
它在运行时开始崩溃,说它无法进行转换。与此同时,我失去了耐心,走上了代码复制的路线... - Farinha
1
这是因为你的集合中还包含其他派生类型。在这种情况下,可以使用OfType方法。 - Jakub Konecki

6

我相信你想要做的是使用泛型:

public class BaseClass<T> where T: BaseItem, new()
{
    public List<T> items;
    protected BaseClass()
    {
        items = new List<T>();
        T item = new T();
        item.SomePropertyOfBaseItem=something;
        items.Add(item);
    }
}

public class DerivedClass: BaseClass<DerivedItem>

这将导致DerivedClass中的items成为List<DerivedItem>where强制只能使用从BaseItem派生的类型。
编辑:“向下转换”,即将类型转换为派生类型,实际上并不是您在此处要做的事情。您的意图是派生列表对象通过设计使用特定的派生项类型,并且您可能希望在派生列表类中存储已实例化的派生类型对象。
因此,这可以很好地工作,而无需使用泛型:List<BaseItem>完全能够存储任何从BaseItem派生的项目。但是,您必须使用强制转换(如其他答案中所述)引用列表中的这些对象,以便访问派生属性。但那只是将对象“转换”为其真实类型。泛型使您能够直接提供强类型访问这些对象的方法。
基本上,在超类的容器中存储对象不会改变对象的任何内容-它只会通过使其看起来是从中派生的较简单类型来更改代码引用该对象的方式。

问题在于,在BaseClass中拥有items属性非常有用,否则我将无法使用BaseClass的构造函数来构建它。 - Farinha
为什么不呢?你的构造函数对于一个 List<BaseItem> 能做到的事情,用 List<T> 也同样容易实现。编译器会根据 where 子句将 T 当作 BaseItem 在基类中处理。 - Jamie Treworgy
顺便说一下 - 泛型可能是自 C# 1.0 以来最有用的补充。我知道,有些人会对喜欢 Linq 或者 = 的人持不同意见。但是,使用泛型可以使您的代码更易读、易懂和抗错误性更强,因为它可以让您强类型化内部成员和返回值,而使用转换则无法做到这一点。 - Jamie Treworgy
@Firinha - 看一下编辑,我想我知道为什么它对你不起作用了。你只需要添加一个new()约束来允许创建泛型类型的项,看一下编辑。 - Jamie Treworgy

0

您还可以使用LINQ来迭代基类的元素并向下转换每个元素,就像这样:

var baseList = new List<BaseClass>();
var derivedList = new List<DerivedClass>();
baseList.ForEach(v => derivedList.Add((DerivedClass)v));

如果您需要在进行强制类型转换之前检查每个元素的派生类型:
var baseList = new List<BaseClass>();
var derivedList = new List<DerivedClass>();
baseList.OfType<DerivedClass>().ToList().ForEach(v => derivedList.Add(v));

更多信息,请查看以下文章:


0
public class Zoo
{
     public List<Animal> items;
     protected BaseClass()
     {         // some code to build list of items     }
 }

public class PettingZoo : Zoo
{
     public PettingZoo : base() {}
}

public class CatComplex : Zoo
{
     public CatComplex : base() {}
}

public class Animal {}
public class Sheep : Animal {}
public class Tiger : Animal {}

...

PettingZoo myZoo = new PettingZoo();
myZoo.items.Add(new Tiger());

PettingZoo和Zoo不可互换,因为PettingZoo应该限制动物的类型。因此,这种设计违反了Liskov替换原则


不好的例子。你的PettingZoo没有对动物类型实施任何限制,因此PettingZoo和Zoo应该是可以互换的。根据你提供的链接,Square和Rectangle类层次结构的测试实现在set_width()和set_height()方法上表示了一个编程错误。Square中的函数通过引入不必要的副作用来违反了重写的Rectangle方法,所以当然测试会失败。 - Suncat2000
@Suncat2000,因此,这个设计违反了Liskov替换原则。 - Amy B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接