从父对象创建子对象的最佳方法

35

我正在从一个父对象创建一个子对象。场景是这样的,我有一个对象和一个子对象,其中子对象添加了一个距离属性,以便在需要搜索的情况下使用。由于我的用户界面可以与搜索对象或对象列表等效工作,而不是位置搜索结果,因此我选择使用继承。因此,在这种情况下,继承似乎是一个明智的选择。

目前,我需要从MyObject的实例生成一个新的对象MyObjectSearch。目前,我正在手动在构造函数中一个一个地设置属性来完成这个过程。我可以使用反射,但这会很慢。是否有更好的方法来实现这种对象增强?

希望下面的代码能说明问题:

public class MyObject {

    // Some properties and a location.
}

public class MyObjectSearch : MyObject {

    public double Distance { get; set; }
    
    public MyObjectSearch(MyObject obj) {
         base.Prop1 = obj.Prop1;
         base.Prop2 = obj.Prop2;
    }
}

还有我的搜索功能:

public List<MyObjectSearch> DoSearch(Location loc) { 
  var myObjectSearchList = new List<MyObjectSearch>();       

   foreach (var object in myObjectList) {
       var distance = getDistance();
       var myObjectSearch = new MyObjectSearch(object);
       myObjectSearch.Distance = distance;
       myObjectSearchList.add(myObjectSearch);
   } 
   return myObjectSearchList;
}

如果只有两类对象(具有和不具有Distance属性的对象),为什么不使用一个具有Distance属性的单一类型,并将其初始化为NaN或其他值,直到特定实例的位置搜索设置了它的值?在少数关键位置,您可以显式地测试未初始化的值。 - BobHy
@Jeroen 我对此有两种想法。我选择继承的原因是因为 UI 可以以完全相同的方式处理 MyObject 和 MyObjectSearch,只显示距离(如果已经进行了搜索)。如果它使用距离搜索或简单列表,则需要根据其工作方式修改 UI。最终的问题是这是一个将被 JSON 序列化的长列表,如果使用组合或空值,则效率会降低。 - Captain John
1
你可以使用AutoMapper将父类型映射到子类型。 - Andrey Burykin
8个回答

44

基类需要定义一个拷贝构造函数:

public class MyObject
{
    protected MyObject(MyObject other)
    {
        this.Prop1=other.Prop1;
        this.Prop2=other.Prop2;
    }

    public object Prop1 { get; set; }
    public object Prop2 { get; set; }
}

public class MyObjectSearch : MyObject
{

    public double Distance { get; set; }

    public MyObjectSearch(MyObject obj)
         : base(obj)
    {
        this.Distance=0;
    }
    public MyObjectSearch(MyObjectSearch other)
         : base(other)
    {
        this.Distance=other.Distance;
    }
}

通过基类处理所有派生类的属性设置。


我认为这是最好的方法,没有通过克隆源对象来实现这一点的可能性。这真的很痛苦。 - rolivares
1
这非常干净,解决了我的深拷贝和继承问题。 - corentinaltepe
这是一个不错的方式,但需要编写代码来复制所有属性值。 - Nitin Sawant
@NitinS - 是的,你需要编写代码来根据项目的要求完成复制。在某些情况下,您需要浅复制,而在其他情况下则需要深复制。这由你决定,这就是为什么它不是自动化的原因。对于自动解决方案,请查看record定义,它们是行为类似于struct的类。 - John Alexiou

25

你可以使用反射来复制属性。

public class ChildClass : ParentClass
{


    public ChildClass(ParentClass ch)
    {
        foreach (var prop in ch.GetType().GetProperties())
        {
            this.GetType().GetProperty(prop.Name).SetValue(this, prop.GetValue(ch, null), null);
        }
    }
}

5
有时候谷歌会显示出你真正想知道的信息,而不管你问了什么。 - CarComp

7
很不幸,没有简单的方法可以实现。如你所说,你需要使用反射或创建一个“Clone”方法来生成一个新的子对象,以父对象作为输入,如下所示:
public class MyObjectSearch : MyObject {

    // Other code

    public static MyObjectSearch CloneFromMyObject(MyObject obj)
    {
        var newObj = new MyObjectSearch();

        // Copy properties here
        obj.Prop1 = newObj.Prop1;

        return newObj;
    }
}

无论如何,你最终都需要编写反射代码(速度较慢),或手动逐个编写每个属性。这完全取决于您是想要可维护性(反射)还是速度(手动属性复制)。

5

一个通用的解决方案是将其序列化为json,然后再反序列化。在json字符串中没有关于哪个类别进行了序列化的信息。

大多数人都是用javascript实现这一点。

正如你所看到的,它对于小型对象很有效,但我不能保证它在每种复杂情况下都能奏效。但是即使在不继承类的情况下,只要属性匹配,它也可以工作。

using Newtonsoft.Json;

namespace CastParentToChild
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var p = new parent();
            p.a=111;
            var s = JsonConvert.SerializeObject(p);
            var c1 = JsonConvert.DeserializeObject<child1>(s);
            var c2 = JsonConvert.DeserializeObject<child2>(s);

            var foreigner = JsonConvert.DeserializeObject<NoFamily>(s);

            bool allWorks = p.a == c1.a && p.a == c2.a && p.a == foreigner.a;
            //Your code goes here
            Console.WriteLine("Is convertable: "+allWorks + c2.b);
        }
    }

    public class parent{
        public int a;
    }

    public class child1 : parent{
     public int b=12345;   
    }

    public class child2 : child1{
    }

    public class NoFamily{
        public int a;
        public int b = 99999;
    }

    // Is not Deserializeable because
    // Error 'NoFamily2' does not contain a definition for 'a' and no extension method 'a' accepting a first argument of type 'NoFamily2' could be found (are you missing a using directive or an assembly reference?)
    public class NoFamily2{
        public int b;
    }
}

3

当我正在寻找如何实现这个功能时,首次遇到了这个问题。如果您能够使用C# 9和record类,您只需要在子类中创建一个新的构造函数,将基类对象传递给子类:

public record MyObject {
  ...
}

public record MyObjectSearch :MyObject 
{
 public MyObjectSearch(MyObject parent) : base(parent) { }
 ...
}

然后你可以像这样创建子对象:
MyObject parent = new();
MyObjectSearch m = new MyObjectSearch(parentObj) { Distance = 1.1};

致谢https://dev59.com/v1IG5IYBdhLWcg3wiAtO#64573044


1
如果浅复制足够,您可以使用 MemberwiseClone 方法
示例:
MyObject shallowClone = (MyObject)original.MemberwiseClone();

如果你需要进行深拷贝,可以像这样进行序列化/反序列化:https://dev59.com/H3VD5IYBdhLWcg3wHn6d#78612 一个示例(假设你按照那个答案建议编写了扩展方法,并将其命名为DeepClone)
MyObject deepClone = original.DeepClone();

0

有一些库可以处理这个问题;但如果你只想在几个地方快速实现,我肯定会选择之前建议的“复制构造函数”。

一个有趣的点没有被提到,那就是如果一个对象是子类,那么它可以从父类中访问子类的私有变量!

所以,在父类中添加一个CloneIntoChild方法。以我的例子为例:

  • Order是父类
  • OrderSnapshot是子类
  • _bestPriceOrder上的非只读私有成员。但是Order可以为OrderSnapshot设置它。

示例:

public OrderSnapshot CloneIntoChild()
{
    OrderSnapshot sn = new OrderSnapshot()
    {
        _bestPrice = this._bestPrice,
        _closed = this._closed,
        _opened = this._opened,
        _state = this._state       
    };
    return sn;
}

注意:只读成员变量必须在构造函数中设置,因此您将不得不使用子构造函数来设置这些...

虽然我通常不喜欢“升级”,但我经常使用这种方法进行分析快照...


0

基本对象似乎应该具有带参数的构造函数来设置其属性:

public class MyObject 
{
    public MyObject(prop1, prop2, ...)
    {
        this.Prop1 = prop1;
        this.Prop2 = prop2;
    }
}

那么,在您的子对象中,您可以有:

public MyObjectSearch(MyObject obj)
    :base(obj.Prop1, obj.Prop2)

这样可以减少与赋值相关的重复。您可以使用反射来自动复制所有属性,但这种方式似乎更易读。

还要注意,如果您的类具有如此多的属性,以至于您正在考虑自动复制属性,则它们很可能违反单一职责原则,您应该考虑更改设计。


我认为这是一个很好的解决方案,因为它比复制或克隆方法更好地保护了所有参数。当你真的想要确保时,可以使用反射或类似的解决方案(例如从属性序列化到和从中序列化,但可能太过繁琐)。 - Patrick Hofman
我不明白为什么需要将所有属性作为参数,而引用基类就足够了。你希望MyObject能够处理自身的复制,这是良好的编程实践。 - John Alexiou
@ja72 只是一个例子 - 这样的构造函数是典型的通用构造函数。根据类的特定用途,您的解决方案或其他答案中提出的解决方案可能更合适。 - BartoszKP
1
我同意Bartosz的观点。如果按照这种方式实现,你不能在构造函数中添加属性而不会导致父类出错。这会提醒你将该字段添加到父类中。 - Patrick Hofman

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