【问题标题】:Cast a list of derived types when casting the parent derived class to it's base type将父派生类转换为它的基类型时转换派生类型列表
【发布时间】:2022-10-14 23:28:42
【问题描述】:

为简洁起见,我简化了代码。

有两个基类DocumentLine 以及从它们派生的两个类DocumentPlusLinePlus

DocumentDocumentPlus 分别包含 List<Line>List<LinePlus>

public class Test
{

    public class Document
    {
        public List<Line> Lines = new List<Line>();
    }

    public class Line
    {
        public string? A;
    }

    public class DocumentPlus : Document
    {
        public new List<LinePlus> Lines = new List<LinePlus>();
    }

    public class LinePlus : Line
    {
        public string? B;
    }
        
    public Test()
    {
        var x = new DocumentPlus();
        x.Lines = new List<LinePlus>()
        {
            new LinePlus() { A = "123", B = "456" },
            new LinePlus() { A = "789", B = "101" },
            new LinePlus() { A = "112", B = "131" }
        };

        var y = (Document)x;

        var z = y.Lines;
        // Z should be the Lines entered above but as their base type
        // Just not sure how to do it!

    }

}

DocumentPlus 实例转换为Document 时,有什么方法可以将List&lt;LinePlus&gt; 转换为List&lt;Line&gt;

谢谢!

【问题讨论】:

  • 理想情况下,我不想更改类结构(因为这反映了在整个程序中大量使用的类),但这种工作更重要。
  • Document 基类中使用(只读)IEnumerable 而不是 List 可以吗? IEnumerable 是协变的,这意味着 List&lt;LinePlus&gt; 既是 IEnumerable&lt;LinePlus&gt; 也是 IEnumerable&lt;Line&gt;
  • 您原来的计划没有奏效,因为它违反了LSP。考虑以下示例代码:DocumentPlus dp = ...; Document d = dp; d.Lines.Add(new SomeOtherSubtypeOfLine());。这通常通过在基类中将 Lines 设为只读来“修复”,从而启用协变。
  • @Heinzi 使用private IEnumerable&lt;Line&gt; _Lines { get; set; } = new List&lt;Line&gt;();,然后分别在DocumentDocumentPlus 中实现两个公共List&lt;Line&gt;new List&lt;LinePlus&gt;,完全按照我想要的方式工作。感谢您的回答!
  • @Heinzi 如果您将其发布为答案,我将接受作为解决方案!

标签: c# inheritance casting


【解决方案1】:

将 DocumentPlus 实例转换为 Document 时,有什么方法可以将 LinePlus 列表转换为 Line 吗?

不,因为List&lt;LinePlus&gt; 属于DocumentPlus 而不是Document

DocumentPlus 在声明 Lines 时使用 new 关键字。这表明您要使用方法隐藏而不是覆盖; Document.Lines 仍然是 List&lt;Line&gt; 类型,并且不引用与 DocumentPlus.Lines 相同的实例。

事实上,它们不能引用同一个实例,因为List&lt;LinePlus&gt;List&lt;Line&gt; 无关,考虑到List&lt;T&gt; 通常是不变的。


如果你只是想兑换DocumentPlus.LinesList&lt;Line&gt;,那么这是完全可能的:

var z = x.Lines.ConvertAll(linePlus => (Line)linePlus);

但似乎您预期的继承层次结构被破坏了。

【讨论】:

  • 是的,我将新关键字放在那里作为临时措施。
【解决方案2】:

尝试这个。

using System.Linq

public Document CastDocPlusToDoc(DocumentPlus inputDocP) {
    Document outputDoc = new Document();
    outputDoc.Lines.AddRange(inputDocP.LinesPlus.Cast<Lines>());
}

var y = CastDocPlusToDoc(x)

我什至不确定 .Cast 在这里是否必要

【讨论】:

  • 我使用了两个答案的组合,在 DocumentPlus 类中放置了一个返回 Document 的方法,并让它分配 base.Lines = Lines.ConvertAll&lt;Line&gt;(p =&gt;p); 然后 return this;
  • 在 Heinzi 建议使用 IEnumerable 之前,我一直使用它,它给出了更理想的语法。不过还是谢谢!
【解决方案3】:

解决这个问题的(可能的)“正确”方法是使用泛型 - 然后不需要强制转换。枚举集合时,您只需获得正确的 Line 类型。

public class Document<TLine> where TLine : Line
{
    public List<TLine> Lines = new List<TLine>();
}

public class Line
{
    public string? A;
}

public class DocumentPlus : Document<LinePlus>
{
}

public class LinePlus : Line
{
    public string? B;
}

现场示例:https://dotnetfiddle.net/BbfOAN


正如评论的那样,这可能更适合您的要求

public class DocumentBase<TLine> where TLine : Line
{
    public List<TLine> Lines = new List<TLine>();
}

public class Line
{
    public string? A;
}

public class Document : DocumentBase<Line>
{
}

public class DocumentPlus : DocumentBase<LinePlus>
{
}

public class LinePlus : Line
{
    public string? B;
}
    

这种方法为您提供类型安全,因此在处理 DocumentPlus 实例时不需要您强制转换为 LinePlus

【讨论】:

  • 感谢您的回答,我试图避免使用泛型,因为我希望在 Document 中使用 Line 和在 DocumentPlus 中使用 LinePlus。这将允许访问LinePlus 中包含的不需要的额外字段。
  • @Avallex 将Document&lt;TLine&gt; 称为DocumentBase&lt;TLine&gt;,然后只定义Document : DocumentBase&lt;Line&gt;DocumentPlus : DocumentBase&lt;LineBase&gt;,现在您具有类型安全性,并且您需要在Document 中使用Line
  • 现在只是玩一玩,我不能将DocumentPlus 实例转换为Document,因为DocumentPlus 不再派生自Document。我需要解决的问题是访问Lines 作为List&lt;Line&gt; 的成员DocumentList&lt;LinePlus&gt; 作为DocumentPlus 的成员
  • @Avallex 我认为您有一个经典的 XYProblem。要么DocumentPlus伊萨Document,在这种情况下,有一个继承层次结构,它继承了Document 的属性(包括它们的类型)。或者它不是,在这种情况下它不会!
  • 另外,关于“我需要解决的问题是将 Lines 作为 List<Line> 作为 Document 的成员访问,并将 List<LinePlus> 作为 DocumentPlus 的成员访问”- 那是确切地通用解决方案解决的问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-26
  • 1970-01-01
  • 1970-01-01
  • 2017-01-08
  • 2013-11-17
相关资源
最近更新 更多