【问题标题】:Convert List of Base Class to a List of Derived Class - possible?将基类列表转换为派生类列表 - 可能吗?
【发布时间】:2017-06-20 18:19:31
【问题描述】:

我的同事告诉我,可以将 List<BaseClass> 转换为 List<DerivedClass>(他们没有向我展示工作示例)。我认为不可能将父类转换为子类(但我知道可以反过来做)。我已经看到几个相关的问题,人们说它不能完成,人们说它可以 - 没有一个提议的解决方案对我有用。我总是得到一个:

System.InvalidCastException.
Unable to cast object of type 'BaseClass' to 'ChildClass'

这绝对可能吗?如果是这样,我做错了什么?这是我的代码(为本问题的目的进行了简化):

父类:

public class ParentClass
{
    public string Name = "";
}

儿童班:

public class ChildClass: ParentClass
{
    public string Date = "";
}

转换:

List<ParentClass> parentList = ServiceApi.GetParentClassList().ToList();
List<ChildClass> childList = parentList.Cast<ChildClass>().ToList();

我的实际代码要复杂得多,但应该适用相同的原则。

【问题讨论】:

标签: c# oop inheritance parent-child


【解决方案1】:

从某种意义上说,你们俩都是对的。

假设您有AnimalCat(继承Animal)和Dog(继承Animal)类型。 List&lt;Animal&gt; 可以同时包含 Cats 和 Dogs,但 List&lt;Dog&gt; 不能包含 Cats。如果您有一个包含 Cat 和 Dog 对象的 List&lt;Animal&gt;,那么您所做的任何事情都不会让您生成包含原始列表中所有内容的 List&lt;Dog&gt;

所以从这个意义上说,你是对的,你不能从 List&lt;ParentClass&gt; 产生 List&lt;DerivedClass&gt;

但有时,作为程序员,您拥有编译器无法获得的有关如何使用对象的信息。如果(且仅当)你可以知道一个特定的List&lt;Animal&gt; 实例只包含Dog 对象,你总是可以这样写:

List<Dog> dogList = myAnimalListWithOnlyDogs.Cast<Dog>().ToList();

再次重申:这仅在您可以保证列表实例中的每个对象都可以转换为 Dog 对象时才有效。如果 Cat 对象以某种方式进入您的列表,您将在运行时遇到异常。尽管如此,这样的代码并不少见。

从这个意义上说,这位同事是对的。当涉及到真正的工作代码时,人们无论如何都会这样做,效果很好,并且可以很好地摆脱它,尽管纯粹主义者可能会争辩说这是一种代码味道,表明需要使用组合而不是继承。

此外,List&lt;Animal&gt; 实例可能同时包含 Cat 和 Dog 对象,而您只关心 Dog 对象。在这种情况下,您可以这样做:

List<Dog> dogList = myAnimalList.OfType<Dog>().ToList();

这种模式在使用 WinForms 控件时特别常见。

在上述两个示例中,您都在使用新序列。在新序列中添加或删除对象不会改变原始序列。从这个意义上说,您没有将原件转换为创建替代件。但是,该序列包含相同的对象,因此在新序列中编辑 Dog 对象的属性更改原始对象中的该对象,因为它是相同的对象实例。

【讨论】:

  • 很好的答案,但是如果 'Dog' 类型仅在运行时已知,我该怎么做 Type dogType = typeof(Dog) ?
  • @AlexandruC 我认为这是对类型系统的滥用,这可能是由于更早之前做出的有缺陷的设计选择导致的,并且通常表明需要将继承设计转换为组合.
  • OfType 是一个很好的建议。感谢您提及!
【解决方案2】:

Cast&lt;T&gt; 方法将强制转换操作应用于输入序列的所有元素。仅当您可以对序列的每个元素执行以下操作而不会导致异常时,它才有效:

ParentClass p = ...
ChildClass c = (ChildClass)p;

除非为p 分配了ChildClass 或其子类之一的实例,否则这将不起作用。在您的情况下,从服务器 API 返回的数据似乎包含 ParentClass 或其子类之一而不是 ChildClass 的对象。

您可以通过构造ChildClass 实例来解决此问题,假设您从服务器获得了足够的信息:

List<ChildClass> childList = parentList
    .Select(parent => new ChildClass(parent.Name, ... /* the remaining fields */))
    .ToList();

【讨论】:

  • 这真的是最糟糕的答案,不知道为什么 OP 选择这个作为接受
  • @CamiloTerevinto 我最好的猜测是,当childList 中没有ChildClass 对象时,这是向OP 展示如何处理这种情况的唯一答案,这很可能发生在您从服务器接收 DTO 对象。
  • 这不完全正确,我的回答还涉及空列表和没有 ChildClass 对象的列表。事实上,所有其他 3 个答案都解决了这个问题
  • @CamiloTerevinto 这只是两个极端案例。看起来 OP 从服务器获取未知子类的对象,需要在客户端将它们转换为已知子类的对象,而不是按类型过滤掉。
  • 如果是这种情况,我宁愿有一个 foreach 将 ChidlClass 添加到列表中,而其他所有内容都转换为具有基本属性的新 ChildClass。为什么?因为您编写它的方式,您根本不会获得任何 ChildClass 属性,这与离开 ParentClass 一样
【解决方案3】:

如果您愿意丢失施法失败的物品,您可以使用OfType() 方法只返回施法成功的物品。

var children = parentList.OfType<ChildClass>().ToList();

【讨论】:

    【解决方案4】:

    未经测试,但这应该可以工作:

    List<ParentClass> parentList = ServiceApi.GetParentClassList().ToList();
    List<ChildClass> childList = parentList
        .Where(x => x is ChildClass)
        .Select(x => x as ChildClass)
        .ToList();
    

    您也可以使用 cmets 中提到的 OfType:

    List<ChildClass> childList = parentList
        .OfType<ChildClass>()
        .ToList();
    

    查看这个 .NET Fiddle:https://dotnetfiddle.net/gDsNKi

    【讨论】:

    • 在这个例子(和小提琴)中,这只有在 List 已经有一个 ChildClass 时才有效。如果 List 里面没有任何 ChildClasses,它将返回 0。是否可以处理 List 只有 BaseClass 对象的情况?然后,获取该列表并将其转换为 List ?
    • @Roka545 在列表中没有 ChildClass 实例的情况下,Where 将返回一个新的List&lt;ChildClass&gt;,为空。 (基本上,它实例化一个新列表并添加过滤器返回 true 的任何对象,因此无论过滤器如何,您总是有一个新列表)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-14
    • 2016-02-26
    • 2017-01-08
    • 2013-11-17
    • 2016-11-06
    相关资源
    最近更新 更多