How to deep copy between objects of different types in C#.NET
Asked Answered
S

9

10

I have a requirement to map all of the field values and child collections between ObjectV1 and ObjectV2 by field name. ObjectV2 is in a different namspace to ObjectV1.

Inheritence between the template ClassV1 and ClassV2 has been discounted as these 2 classes need to evolve independently. I have considered using both reflection (which is slow) and binary serialisation (which is also slow) to perform the mapping of the common properties.

Is there a preferred approach? Are there any other alternatives?

Saprophagous answered 20/2, 2009 at 11:8 Comment(1)
get some actual metrics on the reflection-based approach. You may find that real-world performance is perfectly adequate - I did! If not, go the way Chris Ballard suggested...Emlynn
E
6

As an alternative to using reflection every time, you could create a helper class which dynamically creates copy methods using Reflection.Emit - this would mean you only get the performance hit on startup. This may give you the combination of flexibility and performance that you need.

As Reflection.Emit is quite clunky, I would suggest checking out this Reflector addin, which is brilliant for building this sort of code.

Excerpta answered 20/2, 2009 at 11:26 Comment(0)
W
4

What version of .NET is it?

For shallow copy:

In 3.5, you can pre-compile an Expression to do this. In 2.0, you can use HyperDescriptor very easily to do the same. Both will vastly out-perform reflection.

There is a pre-canned implementation of the Expression approach in MiscUtil - PropertyCopy:

DestType clone = PropertyCopy<DestType>.CopyFrom(original);

(end shallow)

BinaryFormatter (in the question) is not an option here - it simply won't work since the original and destination types are different. If the data is contract based, XmlSerializer or DataContractSerializer would work if all the contract-names match, but the two (shallow) options above would be much quicker if they are possible.

Also - if your types are marked with common serialization attributes (XmlType or DataContract), then protobuf-net can (in some cases) do a deep-copy / change-type for you:

DestType clone = Serializer.ChangeType<OriginalType, DestType>(original);

But this depends on the types having very similar schemas (in fact, it doesn't use the names, it uses the explicit "Order" etc on the attributes)

Westnorthwest answered 20/2, 2009 at 11:15 Comment(2)
Could you give some code examples on how to do that pre-compiled expression stuff and HyperDescriptor?Doe
I've added the 1-liner for PropertyCopy, but caveat (updated): this will do a shallow copy. It would need some effort to do a deep copy (indeed, deep copy simply isn't always possible).Westnorthwest
F
4

You might want to take a look at AutoMapper, a library which specializes in copying values between objects. It uses convention over configuration, so if the properties really have the exaxt same names it will do almost all of the work for you.

Funiculus answered 15/2, 2010 at 14:45 Comment(1)
I wish I could +2 or +3 this :)Tesstessa
S
2

Here is a solution which I built:

     /// <summary>
        /// Copies the data of one object to another. The target object gets properties of the first. 
        /// Any matching properties (by name) are written to the target.
        /// </summary>
        /// <param name="source">The source object to copy from</param>
        /// <param name="target">The target object to copy to</param>
        public static void CopyObjectData(object source, object target)
        {
            CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.Instance);
        }

        /// <summary>
        /// Copies the data of one object to another. The target object gets properties of the first. 
        /// Any matching properties (by name) are written to the target.
        /// </summary>
        /// <param name="source">The source object to copy from</param>
        /// <param name="target">The target object to copy to</param>
        /// <param name="excludedProperties">A comma delimited list of properties that should not be copied</param>
        /// <param name="memberAccess">Reflection binding access</param>
        public static void CopyObjectData(object source, object target, string excludedProperties, BindingFlags memberAccess)
        {
            string[] excluded = null;
            if (!string.IsNullOrEmpty(excludedProperties))
            {
                excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            }

            MemberInfo[] miT = target.GetType().GetMembers(memberAccess);
            foreach (MemberInfo Field in miT)
            {
                string name = Field.Name;

                // Skip over excluded properties
                if (string.IsNullOrEmpty(excludedProperties) == false
                    && excluded.Contains(name))
                {
                    continue;
                }


                if (Field.MemberType == MemberTypes.Field)
                {
                    FieldInfo sourcefield = source.GetType().GetField(name);
                    if (sourcefield == null) { continue; }

                    object SourceValue = sourcefield.GetValue(source);
                    ((FieldInfo)Field).SetValue(target, SourceValue);
                }
                else if (Field.MemberType == MemberTypes.Property)
                {
                    PropertyInfo piTarget = Field as PropertyInfo;
                    PropertyInfo sourceField = source.GetType().GetProperty(name, memberAccess);
                    if (sourceField == null) { continue; }

                    if (piTarget.CanWrite && sourceField.CanRead)
                    {
                        object targetValue = piTarget.GetValue(target, null);
                        object sourceValue = sourceField.GetValue(source, null);

                        if (sourceValue == null) { continue; }

                        if (sourceField.PropertyType.IsArray
                            && piTarget.PropertyType.IsArray
                            && sourceValue != null ) 
                        {
                            CopyArray(source, target, memberAccess, piTarget, sourceField, sourceValue);
                        }
                        else
                        {
                            CopySingleData(source, target, memberAccess, piTarget, sourceField, targetValue, sourceValue);
                        }
                    }
                }
            }
        }

        private static void CopySingleData(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object targetValue, object sourceValue)
        {
            //instantiate target if needed
            if (targetValue == null
                && piTarget.PropertyType.IsValueType == false
                && piTarget.PropertyType != typeof(string))
            {
                if (piTarget.PropertyType.IsArray)
                {
                    targetValue = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
                }
                else
                {
                    targetValue = Activator.CreateInstance(piTarget.PropertyType);
                }
            }

            if (piTarget.PropertyType.IsValueType == false
                && piTarget.PropertyType != typeof(string))
            {
                CopyObjectData(sourceValue, targetValue, "", memberAccess);
                piTarget.SetValue(target, targetValue, null);
            }
            else
            {
                if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName)
                {
                    object tempSourceValue = sourceField.GetValue(source, null);
                    piTarget.SetValue(target, tempSourceValue, null);
                }
                else
                {
                    CopyObjectData(piTarget, target, "", memberAccess);
                }
            }
        }

        private static void CopyArray(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object sourceValue)
        {
            int sourceLength = (int)sourceValue.GetType().InvokeMember("Length", BindingFlags.GetProperty, null, sourceValue, null);
            Array targetArray = Array.CreateInstance(piTarget.PropertyType.GetElementType(), sourceLength);
            Array array = (Array)sourceField.GetValue(source, null);

            for (int i = 0; i < array.Length; i++)
            {
                object o = array.GetValue(i);
                object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
                CopyObjectData(o, tempTarget, "", memberAccess);
                targetArray.SetValue(tempTarget, i);
            }
            piTarget.SetValue(target, targetArray, null);
        }
Sudatory answered 20/2, 2009 at 11:8 Comment(1)
Hey there, I have a question regarding your interesting snippet. How would you manage dupplicating an object within a null target ? I get an exception if I try that.Seiler
C
1

If speed is an issue you could take the reflection process offline and generate code for the mapping of the common properties. You could do this at runtime using Lightweight Code Generation or completely offline by building C# code to compile.

Cu answered 20/2, 2009 at 11:15 Comment(0)
B
1

If if you control the instantiation of the destination object, try using JavaScriptSerializer. It doesn't spit out any type information.

new JavaScriptSerializer().Serialize(new NamespaceA.Person{Id = 1, Name = "A"})

returns

{Id: 1, Name: "A"}

From this it should possible to deserialize any class with the same property names.

Blocking answered 15/2, 2010 at 4:57 Comment(0)
I
1

For a deep copy, I used Newtonsoft and create and generic method such as:

public T DeepCopy<T>(T objectToCopy)
{
    var objectSerialized = JsonConvert.SerializeObject(objectToCopy);
    return JsonConvert.DeserializeObject<T>(objectSerialized);
}

I know that is not much orthodox solution, but it works for me.

Ischia answered 13/7, 2021 at 20:37 Comment(0)
L
0

If speed is an issue, you should implement clone methods in the methods themselves.

Ladyinwaiting answered 20/2, 2009 at 11:12 Comment(2)
Good idea, but this introduces a dependency between the classes that I am trying to avoid. Thanks.Saprophagous
Not necessarily. You could clone the state to a generic format. the classes would just have to be aware of the common protocol.Physiography
E
0
    /// <summary>
    /// Copies matching object's properties from different type objects i.e from source object to destination Type T object
    /// </summary>
    /// <param name="source"></param>
    /// <returns>New Type T object with copied property values</returns>
    public static T CopyPropertiesTo<T>(this object source) where T: new()
    {
        var fromProperties = source.GetType().GetProperties();
        var destination = new T();
        var toProperties = destination.GetType().GetProperties();

        foreach (var fromProperty in fromProperties)
        {
            var fromPropertyType = fromProperty.PropertyType;
            if (Nullable.GetUnderlyingType(fromPropertyType) != null)
            {
                fromPropertyType = Nullable.GetUnderlyingType(fromPropertyType);
            }
            var toProperty = toProperties.FirstOrDefault(x => x.Name.Equals(fromProperty.Name, StringComparison.OrdinalIgnoreCase));
            if (toProperty != null)
            {
                var toPropertyType = toProperty.PropertyType;
                if (Nullable.GetUnderlyingType(toPropertyType) != null)
                {
                    toPropertyType = Nullable.GetUnderlyingType(toPropertyType);
                }

                if (fromPropertyType == toPropertyType)
                {
                    toProperty.SetValue(destination, fromProperty.GetValue(source));
                }
            }
        }
        return destination;
    }
Embodiment answered 15/6, 2023 at 4:3 Comment(2)
You should add explanation.Jhvh
Thank you for contributing to the Stack Overflow community. This may be a correct answer, but it’d be really useful to provide additional explanation of your code so developers can understand your reasoning. This is especially useful for new developers who aren’t as familiar with the syntax or struggling to understand the concepts. Would you kindly edit your answer to include additional details for the benefit of the community?Contortionist

© 2022 - 2024 — McMap. All rights reserved.