Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a cloned copy of subclass from baseclass

Tags:

c#

reflection

Consider this scenario:

public class Base
{
    public int i;
}

public class Sub : Base
{
    public void foo() { /* do stuff */}
}

And then I want to, given an instance of Base get an cloned instance of Sub (with i=17 in this case) so that I can call foo in the subclass.

Base b = new Base { i=17 };
Sub s = CloneAndUpcast(b);
s.foo();

However, how can I create CloneAndUpcast?

I am thinking that is should be possible to recursively clone all of Base-members and properties using reflection. But quite some work.

Anyone with better, neater ideas?

PS. The scenario where I am thinking about using this is a set of "simple" classes in a tree-like structure (no cyclic graphs or similar here) and all the classes are simple value holders. The plan is to have a stupid layer holding all values and then an similar set of classes (the subclasses) that actually contains some business-logic the value-holders shouldn't be aware of. Generally bad practice yes. I think it works in this case.

like image 615
leiflundgren Avatar asked Jan 25 '12 20:01

leiflundgren


3 Answers

You could use AutoMapper to avoid the tedium of writing the copy constructors.

public class MyClass : MyBase
{
    public MyClass(MyBase source)
    {
        Mapper.Map(source, this);
    }
}

and you need to run this once when your application starts up

Mapper.CreateMap<MyBase, MyClass>();

You can download AutoMapper from https://github.com/AutoMapper/AutoMapper

like image 79
Nick Bennett Avatar answered Oct 12 '22 10:10

Nick Bennett


Here's one way (out of many possibilities) that you could do something like you're asking. I'm not sure this is very pretty and can be kind of ugly to debug, but I think it works:

class BaseClass
{
    public int i { get; set; }

    public BaseClass Clone(BaseClass b)
    {
        BaseClass clone = new BaseClass();
        clone.i = b.i;
        return clone;
    }

}

class SubClass : BaseClass
{
    public int j { get; set; }

    public void foo() { Console.WriteLine("in SubClass with value of i = {0}", i.ToString()); }
}

class Program
{
    static void Main(string[] args)
    {
        BaseClass b1 = new BaseClass() { i = 17 };
        BaseClass b2 = new BaseClass() { i = 35 };

        SubClass sub1 = CloneAndUpcast<SubClass>(b1);
        SubClass sub2 = CloneAndUpcast<SubClass>(b2);

        sub1.foo();
        sub2.foo();
    }

    static T CloneAndUpcast<T>(BaseClass b) where T : BaseClass, new()
    {
        T clone = new T();

        var members = b.GetType().GetMembers(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance);
        for (int i = 0; i < members.Length; i++)
        {
            if (members[i].MemberType== MemberTypes.Property)
            {
                clone
                    .GetType()
                    .GetProperty(members[i].Name)
                    .SetValue(clone, b.GetType().GetProperty(members[i].Name).GetValue(b, null), null);
            }

        }
        return clone;
    }
}

Basically, as you suggested, you use reflection to iterate through the object's properties (I set i and j as public properties) and set the values accordingly in the cloned object. The key is using generics to tell CloneAndUpcast what type you're dealing with. Once you do that, it's pretty straightforward.

Hope this helps. Good luck!

like image 7
David Hoerster Avatar answered Oct 12 '22 09:10

David Hoerster


Per the "Gang of Four" : "Favor composition over inheritance" and this is a perfect reason to do so...

If we have a SuperClass that looks like this:

public class SuperClass : Person 

The SuperClass can easily decorate the Person class adding properties not found in Person class. But what happens if the Superclass decorations are only for the GUI? For example a bool value indicating "Selected". We are still able to get all Persons from the DB in a List but we run into trouble trying to create the Superclass and merge the DB results.

    foreach( var person in myPersonList){
    var sc = new SuperClass();
    sc.Selected = false;
    sc=person;
 }

The compiler complains because Superclass is not a Person to the compiler it's a Superclass. The only way to fill in the properties of the Person subclass is to iterate and set each one... like this.

    SuperClass.Name = Person.Name;
    SuperClass.Id = Person.ID;  

Pretty tedious indeed. But there's a better way.... Don't make Superclass inherit from Person

public class SuperClass{
  public Person ThisPerson {get;set;}
  public bool Selected {get;set;}
}

This gives us "Containment" The Superclass now contains a Person class.

Now we can do this:

foreach(var person in MyPersonList){
   var sc = new Superclass();
   sc.Selected = false;
   sc.Person = person;
}

The consumer of this class must now qualify the properties of the Superclass/Person like this...

forach(var sc in MySuperClassList){
  var selected = sc.Selected;
  var name = sc.Person.Name;
}

The beauty of this is that in the future, you can add any other container you want and it will NOT affect any other containers. You can also morph the Superclass to anything it contains. If each of the contained classes become Interfaces, then that's one step futher down the road.

like image 3
JWP Avatar answered Oct 12 '22 11:10

JWP