Click here to Skip to main content
Click here to Skip to main content

Copy Properties From One Object To Another

, 27 Mar 2011 CPOL
Rate this:
Please Sign up or sign in to vote.
Deep copy routine for complex objects that can return a destination type different than the source type.

Introduction

This article describes a method to automatically copy data from an object to another object with similar structures. This is similar to a deep_copy(source, destination) but with the possibility of returning a type that is different from the source.

Background

If you have worked with WCF services, you have noticed that versioning can lead to a lot of code duplication. My problem showed up when a service of version 2.0 was using almost the exact structures as in version 1.0 and I wanted to use the same service workflow code, but with different structures.

I needed a way to apply/convert values from one object to another so I created a method to automatically drill down the source object graph and copy the properties from one object to another. There are many ways to do this, but I designed a method that is generic enough and also leaves place for customization/fine tuning/hacking.

Basically, what I needed was the ability to do this automatically:

service_version1.Customer.Name = service_version2.Customer.Name;
service_version1.Customer.Address.Street = service_version2.Customer.Address.Street;
//service_version1.Customer.Address.Zip = ? // Zip not available in service_version2,
					// skipped

How to Use

Although there are some gotchas, the basic usage is as follows.

  • Include files PropertiesCopier.cs and (optionally) PropertiesCopier.Checks.cs in your project.
  • Call PropertiesCopier.CopyProperties on your source and destination objects.
    PropertiesCopier.CopyProperties(sourceObject, destinationObject);

When to Use

You can use the method described in this article when:

  1. You want to copy the properties from one object to another and the source and destination types are the same.

    An example is:

    form1.label1.Text = anotherForm.label1.Text;
    ...
    form1.textbox1.Text = anotherForm.textbox1.Text;  
  2. You want to copy the properties from one object to another and the source and destination types are different, but similar enough.

    An example is:

    shoes.Color = socks.Color;
    shoes.Fashionable = car.Fashionable;
    //shoes.IsLeather does not have a correspondent in the socks object, 
    //so the automatic copy of properties will just skip this property

    The replacement for the entire code above would be:

    PropertiesCopier.CopyProperties(socks, shoes);

    A better example is:

    service_version1.Customer.Name = service_version2.Customer.Name;
    service_version1.Customer.Address.Street = 
    			service_version2.Customer.Address.Street;

    The replacement for the entire code above would be:

    PropertiesCopier.CopyProperties(service_version2, service_version1);
  3. Similar to points 2 and 3 but with the difference that some corresponding property types have changed.

    An example is:

    string service_version1.Customer.Address.Zip;
    int service_version1.Customer.Address.Zip;
      
    service_version1.Customer.Address.Zip = service_version2.Customer.Address.Zip;  

    In this case, you can manually edit the source code and create something like:

    ...
    original algorithm: get source property and value
      
    hack: if (source property == 'Customer.Address.Zip') 
    		{ convert value to destination type}
      
    original algorithm: set value to destination
    ...

    This is not a recommended hack, but if the hack/exception count is low, then the benefits of the automation should outnumber the downsides of hard coded hacks.

When Not to Use

The method described in this article does not support generic types out of the box. I leave it to you as an exercise to patch the algorithm to support generics.

Implementation

The CopyProperties is the entry point of the copying properties algorithm - here you can set up counters and other debug info.

public static void CopyProperties(object source, object destination)
{
    var count = 1;

    CopyPropertiesRecursive(source, destination, null, ref count);

    Console.WriteLine("Counted: " + (count - 1));
}

The CopyPropertiesRecursive is where all the magic happens (a pseudo code description of the code is a few lines below).

One of the parameters of this method is propertiesToOmmit. You may need to exclude the ExtensionData property - for the unfamiliar with WCF: the ExtensionData property is automatically generated by the wsdl tool and is usually unused.

private static void CopyPropertiesRecursive
		(object source, object destination,
            IList<string> propertiesToOmmit, ref int count)
{
    var destinationType = destination.GetType();
    
    Type sourceType = null;

    if (source != null)
        sourceType = source.GetType();

    var destinationProperties = destinationType.GetProperties();

    //for a type coming from a serialized web service type
    if (propertiesToOmmit == null)
        propertiesToOmmit = new List<string> { "ExtensionData" };

    destinationProperties = Array.FindAll
	(destinationProperties, pi => !propertiesToOmmit.Contains(pi.Name));

    foreach (var property in destinationProperties)
    {
        var propertyType = property.PropertyType;

        //todo can cache this as: static readonly 
	//Dictionary<type,object> cache. how about multithreading?
        var sourceValue = propertyType.IsValueType ? 
		Activator.CreateInstance(propertyType) : null;

        PropertyInfo propertyInSource = null;
        var sourceHasDestinationProperty = false;

        //source is null
        if (source != null)
        {
            propertyInSource = sourceType.GetProperty(property.Name);

            //source has the property
            if (propertyInSource != null)
            {
                sourceHasDestinationProperty = true;
                sourceValue = propertyInSource.GetValue(source, null);
            }
            else Console.WriteLine("\tsource does not contain property " + 
			destinationType + " -> " + property.Name);
        }
        //else
        //  Console.WriteLine("\tsource was null for " + 
	// destinationType + " -> " + property.Name);


        //it's a complex/container type?
        var isComplex = !propertyType.ToString().StartsWith("System");

        if (isComplex & !propertyType.IsArray)
        {
            Console.WriteLine("\tRecursion on: " + property.Name);

            //create new destination structure

            var ci = propertyType.GetConstructor(Type.EmptyTypes);
            var newDestination = ci.Invoke(null);

            property.SetValue(destination, newDestination, null);

            //Console.WriteLine("\tCalled constructor on " + property.Name);

            CopyPropertiesRecursive(sourceValue, newDestination, 
				propertiesToOmmit, ref count);
            continue;
        }

        var s = count + ". " + property.Name + 
		(propertyType.IsArray ? "[]" : "") + " = ";

        if (!sourceHasDestinationProperty)
            s += "[default(" + propertyType + ")] = ";

        Console.WriteLine(s + sourceValue);


        //todo check for CanWrite and CanRead - if (!toField.CanWrite) continue;

        if (propertyType.IsArray & propertyInSource != null)
            sourceValue = DeepCopyArray(propertyInSource.PropertyType, 
			propertyType, sourceValue, source, destination);

        property.SetValue(destination, sourceValue, null);

        var destinationValue = property.GetValue(destination, null);

        count++;

        //todo deep assert for arrays
        if (!propertyType.IsArray)
            Assert.AreEqual(sourceValue, destinationValue, 
		"Assert failed for property: " + destinationType + "." + 
			property.Name);
    }
}

The pseudocode for CopyPropertiesRecursive looks like:

void CopyPropertiesRecursive(source, destination)
{
    foreach(var property in destination.Properties)
    {
        T = property.PropertyType;
        
        bool doesSourceHaveProperty = ...;
        
        object sourceValue = doesSourceHaveProperty ? getSourceValue() : default(T);
        
        if (T is service specific structure AND is not array)
        {           
           property.Value = Constructor(T);
           
           CopyPropertiesRecursive(sourceValue, 
		property.Value); //go recursive on this property
           
           continue;
        }
        
        if (T is array)
            sourceValue = DeepCopyArray(sourceValue, new type T);
        
        property.Value = sourceValue;        
    }
}

The DeepCopyArray is a routine that takes a source array, serializes it to XML, changes the source type name/namespace to destination type name/namespace and deserializes it as an array to type destination.

This method is slow on performance if the array is big, so you may want to replace this code with some reflection code similar to CopyPropertiesRecursive.

private static object DeepCopyArray(Type sourceType, 
	Type destinationType, object sourceValue, object sourceParent, 
	object destinationParent)
{
    //todo this method ca be made generic and handle more than just arrays

    if (sourceValue == null || sourceType == null || 
		sourceParent == null || destinationParent == null)
        return null;

    using (var stream = new MemoryStream(2 * 1024))
    {
        var serializer = new DataContractSerializer(sourceType);

        serializer.WriteObject(stream, sourceValue);

        serializer = new DataContractSerializer(destinationType);

        
        //if we know the namespace or type names will be different, 
	//we must get the xml and REPLACE the
        //source type name/namespace to destination source type name/namespace
        
        //if the namespace/type name combination is the same, 
	//we do not need the if TRUE statement,
        //only the ELSE branch
        
        if (true)
        {
            var xml = Encoding.UTF8.GetString(stream.ToArray());

            // our example array serialization looks like below:
            // <ArrayOfV1KeyValuePair 
	   // xmlns="urn:mycompany.com/data/version_1.0" 
	   // xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
            //    <V1KeyValuePair>
            //        <Key>jet key 1</Key> 
            //        <Value>jet value 1</Value> 
            //    </V1KeyValuePair>
            //    <V1KeyValuePair>
            //        <Key>jet key 2</Key> 
            //        <Value>jet value 2</Value> 
            //    </V1KeyValuePair>
            // </ArrayOfV1KeyValuePair>

            //replace source type name with destination type name
            
            var nameSource = sourceType.Name.Replace("[]", "");
            var nameDestination = destinationType.Name.Replace("[]", "");

            xml = xml.Replace(nameSource, nameDestination);

            //replace source namespace with destination namespace
            
            var sourceNamespace = GetDataContractNamespace(sourceParent);
            var destiantionNamespace = GetDataContractNamespace(destinationParent);

            xml = xml.Replace(sourceNamespace, destiantionNamespace);

            using (var modified = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                modified.Position = 0;

                return serializer.ReadObject(modified);
            }
        }
        else
        {
            stream.Position = 0;

            return serializer.ReadObject(stream);
        }
    }
}

private static string GetDataContractNamespace(object instance)
{
    if (instance == null)
        throw new ArgumentNullException("instance");

    var attribute = instance.GetType().GetCustomAttributes
	(true).Single(o => o.GetType() == typeof(DataContractAttribute));

    return ((DataContractAttribute)attribute).Namespace;
}

How is this Algorithm Different from What is Already Available?

  1. The most important feature is that the destination type can be different from the source type. This allows WCF service versioning, but can also be used in other ways, like copying properties of a Windows Form to another Form.

  2. The algorithm drills down the destination properties - it recurses through all the object graph.

  3. If the destination does not contain a property from the source, then this property is skipped.

  4. If the destination does contain a property that is not in the source, the destination property and its sub graph is initialized to the default values(s).

    In other words: the default constructor is called on the destination property and sub properties if no corresponding property is found in the source.

    This is very helpful in the following scenario:

    //we manually copy properties
    service_version1.Customer.Name = service_version2.Customer.Name;
    //service_version1.Customer.Address.Zip  = ? // service_version2 
    				//does not have a Customer.Address class
    //we forget to/do not initialize Address and sub properties
        
    //we now want to access service_version1.Customer.Address.Zip
    if (service_version1.Customer.Address.Zip == 6789)
    //this will throw a null reference exception for Address.Zip 
    {
        DoSomething();
    }

    This algorithm takes care of this scenario, and initializes all properties to default if nothing or null is found in the source.

  5. The algorithm contains a deep copy array method (useful for a little bit more complex types than primitives) - albeit not the most performance friendly.

Conclusion

I created this method to copy properties from one object to another because it suited my needs the best and I could not find a complete solution like this on the web.

I leave it to you to fine tune this code according to your needs and leave you with a few suggestion for improvement.

Improvements

  • Read about other methods of shallow copying and deep copying and decide which one fits your requirements better.
  • You can remove a lot of extra debugging information.
  • If source and destination types are the same, you can optimize a lot of code especially in the DeepCopyArray method.
  • When initializing the sourceValue to default(T) you can cache the Activator.CreateInstance(propertyType) to a Dictionary<type,object>.
  • Add testing for arrays - see if the elements in the destination array are equal to the ones in the source.

History

  • 27th March, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Adrian Vintu

Austria Austria
Adrian is an old timer who has had the wonderful experience of working in a variety of quality environments and with various quality people.
 
Throughout time he has got down with Assembler, C++, Borland Delphi, Java, C#, Android etc.
He took part in projects concerning software security, industrial controllers, health-food and health-care ERPS, AI simulations, data mining and mobile development.
 
Adrian is always interested in the big picture of the things and wishes to become a great architect one day.
 
Adrian maintains a blog here http://adrianvintu.com[^] and is trying to make it as nice and helpful and user friendly as possible.

Comments and Discussions

 
GeneralMy vote of 3 Pinmemberafaz28-Mar-11 23:13 
GeneralPlus +1 for AutoMapper PinmvpSacha Barber28-Mar-11 2:47 
GeneralAutoMapper PinmemberRamon Smits28-Mar-11 2:43 
GeneralRe: AutoMapper PinmemberAdrian Vintu28-Mar-11 3:31 
GeneralAn idea PinmemberSarafian28-Mar-11 0:03 
GeneralRe: An idea PinmemberAdrian Vintu28-Mar-11 1:02 
GeneralRe: An idea PinmemberSarafian28-Mar-11 1:07 
GeneralTry fastJSON PinmemberMehdi Gholam27-Mar-11 11:11 
GeneralRe: Try fastJSON PinmemberAdrian Vintu27-Mar-11 12:15 
Nice idea w/ JSON - but of course there are always pros and cons to choosing one method over another.
 
Feel free to make some performance tests, I am interested in the results.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411022.1 | Last Updated 27 Mar 2011
Article Copyright 2011 by Adrian Vintu
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid