2

I am currently building an object model for HL7 messages. Without diving into those, the basic structure that we have looks similar to this:

  • Base Object
    • Intermediary Object
      • DeepMessage1
      • DeepMessage2
    • Message1
    • Message2

I want to have a deep copy/clone that will copy all of the similar properties from DeepMessage1 to DeepMessage2 or Message1 or Message2.

public class BaseObject
{
    PersonName Name; //A personName includes a first name and last name.
}

public class IntermediaryObject : BaseObject
{
    Accident accident; //An accident codes and a description.
}

public class Message1 : BaseObject
{
    //this class doesn't actually contain any special handling, just used for 
    //easy understanding for the API
}

public class Message2 : BaseObject
{
    DateTime AdmissionDate; //Note the admission date is also contained in
                            //DeepMessage1, but not DeepMessage2
}

public class DeepMessage1 : IntermediaryObject
{
    DateTime AdmissionDate; //Note this property is also contained in Message2 and 
                            //needs to be copied
}

public class DeepMessage2 : IntermediaryObject
{
    DateTime DischargeDate;
}

With that structure in mind, I want to be able to create a deep copy of every property that one object shares with another. This other question was a really good start, but ultimately I couldn't use reflection as that was shallow cloning, and serialization requires the exact same object.

I ended up with this code, but it only performs a shallow copy.

public T Copy<T>() where T : new()
{
    T destination = new T();
    if (destination is HL7Message)
    {
        foreach (var destinationProperty in destination.GetType().GetProperties())
        {
            foreach (var sourceProperty in this.GetType().GetProperties())
            {
                if (destinationProperty.Name.Equals(sourceProperty.Name))
                {
                    destinationProperty.SetValue(destination, destinationProperty.GetValue(this, null), null);
                }
            }
        }
        return destination;
    }
    else
    {
        throw new InvalidOperationException("The destination copy type is not an HL7Message object");
    }
}

I was hoping that in my if (destinationProperty.Name.Equals(sourceProperty.Name)) block I could attempt to call Copy on any properties that are of a specific base type (which all of the objects in my library extend). However, I couldn't get the Copy in that section to work as I can't determine the T at runtime.

Do I just need to have a separate type of Copy for the specific objects and have the Messages use the Copy or is there a way to do this that is so damn crazy?

Community
  • 1
  • 1
Quickhorn
  • 1,171
  • 12
  • 23
  • If the properties you want to copy are DateTime the deep copy/shallow copy thing is irrelevance DateTime is a value type a copy of the value because there is no reference to copy. – Ben Robinson Dec 01 '11 at 00:16

2 Answers2

3

This is a complete tool for copying properties between objects, take a look:

https://github.com/AutoMapper/AutoMapper/wiki

Marcelo De Zen
  • 9,439
  • 3
  • 37
  • 50
  • I'm looking through the automapper, would I need to map say AdmissionDate to AdmissionDAte as well as Message2 to DeepMessage1, or would that map be implied since they share property names? The example they have seems to imply that you must map every object that isn't a standard type. This would make the mapping a little unreasonable for a library as big as ours. – Quickhorn Dec 01 '11 at 18:14
  • You can easily develop a custom value resolver that ignores case with a few lines of code. – Marcelo De Zen Dec 03 '11 at 03:44
  • Sorry, the issue was not the case difference. That was a typo. Instead, the question was around the process of mapping, and whether I had to map AdmissionDate to AdmissionDate as well as Message2 to DeepMessage1 (which both contain AdmissionDate). It was a concern for the length of the map, considering the number of objects that I must map. – Quickhorn Jan 05 '12 at 18:43
1

In order to solve this problem, I ended up with a 2-tier approach. I am not making any assertions that the map mentioned by devman could not have worked, but with limited time I was not able to explore his solution.

First, all of my objects extended the same base object (BaseLibraryClass for the example). I originally did this because every class was going to original inherit INotifyPropertyChanged. To handle raising the property changed, I created a base object all of my objects inherited from. On this base class, I also added an abstract DeepCopy() method. Every object would override the DeepCopy() method by creating a new instance of itself and assigning the values over or, in the case of custom properties, call DeepCopy() on those properties.

Class Cat : BaseLibraryClass
{
    public string Name;
    public Collar MyCollar;

    public override object DeepCopy()
    {
        Cat destination = new Cat();
        Cat.Name = Name;
        Cat.MyCollar = MyCollar.DeepCopy();
    }
}

Class Collar { ... }

Now I combined this with a modified version of the reflection loop in my original question. All of the objects that I wanted to be able to do this sort of Copy inherited the same base class. In the example case, an BaseCopyObject.

public T CopyObject<T>() where T : new()
{
    T destination = new T();
    if (destination is BaseCopyObject)
    {
        foreach (var destinationProperty in destination.GetType().GetProperties()) //iterate through the properties of the destination object.
        {
            foreach (var sourceProperty in this.GetType().GetProperties()) // iterate through the properties of the source object to determine if the property names match. If they do, copy the value.
            {
                //If we have one of our BaseCopyObjects, then set the value with the DeepCopy() method of that object.
                if (destinationProperty.Name.Equals(sourceProperty.Name))
                {
                    if (typeof(BaseLibraryClass).IsAssignableFrom(destinationProperty.PropertyType))
                    {
                        BaseCopyObject var = (BaseLibraryClass)sourceProperty.GetValue(this, null);
                        if (var != null)
                        {
                            destinationProperty.SetValue(destination, var.DeepCopy(), null);
                        }
                        break;
                    }
                    //If we have a list, get the list and iterate through the list objects.
                    else if (typeof(IList).IsAssignableFrom(destinationProperty.PropertyType))
                    {
                        IList destinationList = (IList)destinationProperty.GetValue(destination, null);
                        destinationList.Clear();
                        IList sourcelist = (IList)destinationProperty.GetValue(this, null);
                        if (sourcelist != null)
                        {
                            foreach (object listItem in sourcelist)
                            {
                                if (listItem is Base)
                                {
                                    BaseLibraryClass n = (BaseLibraryClass)listItem;
                                    destinationList.Add(n);
                                }
                            }
                        }
                        break;
                    }
                    //Finally we get to normal properties. Set those.
                    else
                    {
                        destinationProperty.SetValue(destination, destinationProperty.GetValue(this, null), null);
                        break;
                    }

                }
            }
        }
        return destination;
    }
    else
    {
        throw new InvalidOperationException("The destination copy type is not an BaseLibraryClass object");
    }
}
Quickhorn
  • 1,171
  • 12
  • 23