Best way to create instance of child object from parent object
Asked Answered
B

8

38

I'm creating a child object from a parent object. So the scenario is that I have an object and a child object which adds a distance property for scenarios where I want to search. I've chosen to use inheritance as my UI works equivalently with either a search object or a list of objects not the result of a location search. So in this case inheritance seems a sensible choice.

As present I need to generate a new object MyObjectSearch from an instance of MyObject. At present I'm doing this in the constructor manually by setting properties one by one. I could use reflection but this would be slow. Is there a better way of achieving this kind of object enhancement?

Hopefully my code below illustrates the scenario.

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;
    }
}

And my search function:

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;
}
Biffin answered 30/12, 2013 at 15:4 Comment(3)
If there are just the 2 classes of objects (those with and without the Distance property), why not just use a single type that has the Distance property and initialize it to NaN or something until a location search on a specific instance sets a value for it? In the few places where it matters, you can explicitly test for the uninitialized value.Soong
@Jeroen I am in two minds about this. The reason I settled on inheritance is because the UI works with both the MyObject and MyObjectSearch in exactly the same way displaying only a distance if it has been subject to a search. I'd need to modify the UI depending on if it worked with a Distance search or a simple list. The final angle is this is a long list that will be JSON serialized, if it uses composition or null value it will less efficient.Biffin
You can use AutoMapper to map parent type to child.Alsoran
M
46

The base class needs to define a copy constructor:

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;
    }
}

This way the setting of properties is handled for all derived classes by the base class.


Optional is to implement ICloneable also

public class MyObjectSearch : MyObject, ICloneable
{
    public MyObjectSearch(MyObjectSearch other)
        : base(other)
    {
        ...
    }

    ...

    public MyObjectSearch Clone()
    {
        return new MyObjectSearch(this);
    }
    object ICloneable.Clone()
    {
        return Clone()
    }
}
Milan answered 30/12, 2013 at 15:14 Comment(4)
I think this is the best approach, there is no possibility to do this by cloning the source object. It's a really pain.Wat
This is very clean and fixes my issues with deep copy and inheritance.Thorley
this is good way but need to write code for copying all the property valuesCrackdown
@NitinS - yes you need to write code to do the copying per the requirements of the project. In some cases you need a shallow copy and others a deep copy. It is up to you, and this is what it is not automated. For an automatic solution look into record definitions which are classes that behave like struct.Milan
T
26

You can use reflection to copy properties.

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);
        }
    }
}
Tennes answered 29/11, 2017 at 16:45 Comment(1)
Sometimes google shows you what you really wanted to know, regardless of what you asked.Photophobia
A
7

There is no easy way to do this, unfortunately. As you said, you would either have to use reflection, or create a "Clone" method that would generate a new child object using a parent object as input, like so:

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;
    }
}

No matter what, you're either going to end up writing reflection code (which is slow), or writing each property out by hand. It all depends on whether or not you want maintainability (reflection) or speed (manual property copy).

Alagoas answered 30/12, 2013 at 15:12 Comment(0)
T
6

A generic solution would be to serialize it to json and back. In the json-string is no information about the class name from which it was serialized. Most people do this in javascript.

As you see it works well for pocco objects but i don't guarantee that it works in every complex case. But it does event for not-inherited classes when the properties are matched.

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;
    }
}
Trouvaille answered 1/9, 2017 at 7:27 Comment(0)
S
3

I first came accros this question when I was looking for doing this. If you are able to work with C# 9 and record-classes. You only have to create a new constructor in the sub-class taking in a base class object and hand it over to the subclass:

public record MyObject {
  ...
}

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

Then you can create the child object like this:

MyObject parent = new();
MyObjectSearch m = new MyObjectSearch(parentObj) { Distance = 1.1};

Credits to https://mcmap.net/q/410528/-using-c-9-records-quot-with-quot-expression-can-i-copy-and-add-to-new-derived-instance

Sato answered 12/4, 2022 at 7:50 Comment(0)
D
1

If a shallow copy is enough, you can use the MemberwiseClone method.

Example:

MyObject shallowClone = (MyObject)original.MemberwiseClone();

If you need a deep copy, you can serialize/deserialize like this: https://mcmap.net/q/9283/-deep-cloning-objects

An example (assuming you write an extension method as suggested in that answer, and you call it DeepClone)

MyObject deepClone = original.DeepClone();
Disremember answered 30/12, 2013 at 15:22 Comment(0)
M
0

Seems natural for the base object to have constructor with parameters for its properties:

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

So then, in your descendant object you can have:

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

This reduces duplication related to assignments. You could use reflection to automatically copy all properties, but this way seems more readable.

Note also, that if your classes have so much properties that you're thinking about automatizing of copying of the properties, then they are likely to violate the Single Responsibility Principle, and you should rather consider changing your design.

Methodology answered 30/12, 2013 at 15:13 Comment(4)
I think this is a good solution, since it is a better safeguard for passing all parameters then the copy or clone methods. When you really want to be sure, use reflection or similar solutions (like serializing properties from and to, but that might be too much)Gavette
I do not see why have all the properties as arguments when a reference to the base class would suffice. You want MyObject to handle copying itself as good programming practices.Milan
@ja72 Just an example - such constructor is a typical general-purpose constructor. Depending on particular usage of the class, your solution or others proposed in other answers might be suitable.Methodology
I agree with Bartosz. When implementing it like this, you cannot add a property in the constructor without an error in the parent class. This reminds you to add the field to the parent class.Gavette
A
0

There are libraries to handle this; but if you just want a quick implementation in a few places, I would definitely go for a "copy constructor" as previously suggested.

One interesting point not mentioned is that if an object is a subclass, then it can access the child's private variables from the within the parent!

So, on the parent add a CloneIntoChild method. In my example:

  • Order is the parent class
  • OrderSnapshot is the child class
  • _bestPrice is a non-readonly private member on Order. But Order can set it for OrderSnapshot.

Example:

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

NOTE: Readonly member variables MUST be set in the constructor, so you will have to use the child constructor to set these...

Although I don't like "up-sizing" generally, I use this approach a lot for analytic snapshots...

Admass answered 9/7, 2017 at 6:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.