All posts tagged Deep copy

In A different take on Deep Copy I talked a bit about the different approaches to how you can accomplish performing a Deep Copy of a model in C#. In this article I want to revisit the topic briefly and demonstrate two methods to handle this concern.

Q: Why not use Object.MemberwiseClone?
A: You can, but it only performs a shallow copy of your model. If you have a more complex model, you will need something that can dig deeper and map all your properties.

The first approach is our Deep Copy method done with serialization. If you have been around the Internet a bit, you will recognize it:


/// <summary>
/// Performs a Deep Copy of an object through serialization.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="oSource">The o source.</param>
/// <returns></returns>
public static T DeepCopy<T>(this T oSource)
{
    using (var ms = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(ms, oSource);
        ms.Position = 0;
        return (T)formatter.Deserialize(ms);
    }
}

This is the cleanest way to copy a model.

1) A MemoryStream is created.
2) A BinaryFormatter serializes the source into the MemoryStream.
3) The MemoryStream position is reset for reading purposes.
4) A deserialized object is cast to type T and returned.

A simple example:


var myNewObject = myObject.DeepCopy();

 
This works great in many cases but not in a Framework like Silverlight. Given, not a lot of us use that anymore, but you could still run into a scenario where serialization is not possible. In that case, we are going to create a new instance of the model we intend to copy. Then, we will map properties between the 2 models.


/// <summary>
/// Copies all public properties from one class to another.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
/// <exception cref="System.Exception">Source and destination cannot be null and must be 
/// of the same type!</exception>
public static void MapProperties<T>(this T source, T destination) where T : class
{
    // Source and destination must exist.
    if ((source == null || destination == null)) return;

    // Get properties
    var propertyInfos = source.GetType().GetProperties();
    if (!propertyInfos.Any()) return;

    // Process only public properties
    foreach (var propInfo in propertyInfos.Where(x => x.CanWrite))
    {
        // Get value from source and assign to destination.
        var value = propInfo.GetValue(source, null);
        if (value == null) continue;

        // Evaluate
        var valType = value.GetType();

        // Collections
        if (valType.InheritsFrom<ICollection>())
        {
            var sourceCollection = value as IList;
            if (sourceCollection == null) continue;

            // Create new instance of collection
            IList destinationCollection = null;
            destinationCollection = (valType.BaseType == typeof(Array))
                ? Array.CreateInstance(valType.GetElementType(), sourceCollection.Count)
                : (IList)Activator.CreateInstance(valType, null);
            if (destinationCollection == null) continue;

            // Map properties
            foreach (var item in sourceCollection)
            {
                // New item instance
                var newItem = HasDefaultConstructor(item.GetType()) 
                    ? Activator.CreateInstance(item.GetType(), null) 
                    : item;

                // Map properties
                item.MapProperties(newItem);

                // Add to destination collection
                if (valType.BaseType == typeof(Array))
                {
                    destinationCollection[sourceCollection.IndexOf(item)] = newItem;
                }
                else
                {
                    destinationCollection.Add(newItem);
                }
            }

            // Add new collection to destination
            propInfo.SetValue(destination, destinationCollection, null);
        }
        else
        {
            propInfo.SetValue(destination, value, null);
        }

        // Check for properties and propagate if they exist.
        var newPropInfos = value.GetType().GetProperties();
        if (!newPropInfos.Any()) continue;

        // Copy properties for each child where necessary.
        var childSource = source.GetType().GetProperty(propInfo.Name);
        var childDestination = destination.GetType().GetProperty(propInfo.Name);
        childSource.MapProperties(childDestination);
    }
}

/// <summary>
/// Determines whether the type has a default contructor.
/// </summary>
/// <param name="type">The type.</param>
/// <returns></returns>
private static bool HasDefaultConstructor(Type type)
{
    return
        type.GetConstructor(Type.EmptyTypes) != null ||
        type.GetConstructors(BindingFlags.Instance | BindingFlags.Public)
            .Any(x => x.GetParameters().All(p => p.IsOptional));
}

Here is what is going on:

1) We get the properties associated with the class type.
2) We evaluate the public properties.
3) Using reflection, we retrieve the value from the source and apply it to the destination.
4) If the value is not null, we drill down further for more public properties recursively.
Note: Collections need a bit of extra work to ensure we don’t simply copy over their object instances.
5) Repeat until the entire model has been traversed.

A simple example:


SomeType myNewObject = new SomeType();

// Assumption: "myObject" is of type "SomeType"
myObject.MapProperties(myNewObject);

In conclusion: if you can’t serialize, map the properties between the 2 models.

That’s all for now.

A different take on Deep Copy

Categories: .Net, C#, Clone, Silverlight, WPF
Comments: 1

This article has been revised here.

I have seen several examples of different methods to perform a deep copy in C#. The most common one is with a Stream, implemented like this:


public static T DeepClone<T>(T obj)
{
 using (var ms = new MemoryStream())
 {
   var formatter = new BinaryFormatter();
   formatter.Serialize(ms, obj);
   ms.Position = 0;

   return (T) formatter.Deserialize(ms);
 }
}

That is certainly one way to do it but it won’t work in Silverlight.

The suggested Sliverlight approach:


public static T DeepCopy<T>(this T oSource)
{
    T oClone;
    DataContractSerializer dcs = new DataContractSerializer(typeof(T));

    using (MemoryStream ms = new MemoryStream())
    {
        dcs.WriteObject(ms, oSource);
        ms.Position = 0;
        oClone = (T)dcs.ReadObject(ms);
    }

    return oClone;
}

All right, that works in Silverlight but it requires that all objects passed are Serializable. That is actually a bit of a daunting task for custom objects at times, and we should try to use a simpler more universal approach.

So, I suggest using reflection:

/// <summary>
/// Copies all public properties from one object to another.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
/// <exception cref="System.Exception">Source and destination cannot be null and must be 
/// of the same type!</exception>
public static void DeepCopy(object source, object destination)
{
    if ((source != null &amp;&amp; destination != null) &amp;&amp; 
        (source.GetType() == destination.GetType()))
    {
        // Get properties
        var propertyInfos = source.GetType().GetProperties();
        if (propertyInfos.Length <= 0) return;

        // Evaluate
        foreach (var propInfo in propertyInfos)
        {
            // Process only public properties
            if (!propInfo.CanWrite) continue;

            // Get value from source and assign to destination
            object value = propInfo.GetValue(source, null);
            propInfo.SetValue(destination, value, null);
            if (value == null) continue;

            // Check for properties and propogate if they exist
            var newPropInfos = value.GetType().GetProperties();
            if (newPropInfos.Length > 0)
            {
                // Copy properties for each child where necessary
                DeepCopy(
                    source.GetType().GetProperty(propInfo.Name),
                    destination.GetType().GetProperty(propInfo.Name));
            }
        }
    }

    else
    {
        throw new Exception(
            "Source and destination cannot be null and must be of " +
            "the same type!");
    }
}

This will work in Silverlight because of the if block for propInfo.CanWrite. This is added because based on Silverlights rigid security constraints we can only copy public properties.

That’s all for now.