-1

I am trying to deep copy a complex object

public class Team
{
    public string id { get; set; }
    public Driver driver{ get; set;}
    public Driver codriver{ get; set;}
}

public class Driver
{
    public Team parentTeam{ get; set;}
    public string driverId { get; set; }
}

var modifiedTeams = new List<Team>
{
    new Team {id = "T1", driver = new Driver { driverId = "D2" }, codriver = new Driver { driverId = "C1"} },
    new Team {id = "T2", driver = new Driver { driverId = "D1"} }
};

//Note: Parent Team holds reference to the Team the driver or codriver is in

I want to deep copy the modified lists. Below is the utility that I am using.

    /// <summary>  
/// Utility   
/// </summary>  
public static class DeepCopyUtility
{
    private static string[] _excludedPropertyNames = null;
    private static BindingFlags _memberAccess = (BindingFlags.Public | BindingFlags.NonPublic | 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 public</param> 
    /// <param name="propertyNames">     </param> 
    public static void DeepCopy<T>(T source, T target, string[] propertyNames = null)
    {
        if (source == null)
        {
            throw new ArgumentNullException("Object is null");
        }
        if (propertyNames != null)
        {
            _excludedPropertyNames = propertyNames;
        }
        CopyObjectData(source, target, new Dictionary<object, object>());
    }

    /// <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> 
    public static void CopyObjectData(object source, object target, Dictionary<object, object> cloned)
    {
        Type type = source.GetType();
        if (target == null && type.IsValueType == false && type != typeof(string))
        {
            if (source.GetType().IsArray)
            {
                target = Activator.CreateInstance(source.GetType().GetElementType());
            }
            else if (source.GetType().IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
            {
                if (typeof(IList).IsAssignableFrom(type))
                {
                    target = (IList)Activator.CreateInstance(type);
                }
                else if (type.IsGenericType)
                {
                    var objectType = type.GetGenericArguments().Single();
                    if (typeof(IList<>).MakeGenericType(objectType).IsAssignableFrom(type) || typeof(ISet<>).MakeGenericType(objectType).IsAssignableFrom(type))
                    {
                        target = Activator.CreateInstance(type);
                    }
                }
            }
            else
            {
                target = Activator.CreateInstance(source.GetType());
            }
        }

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

            // Skip over excluded properties   
            if (_excludedPropertyNames != null && _excludedPropertyNames.Contains(name))
            {
                continue;
            }

            object clone;
            if (cloned.TryGetValue(source, out clone))
            {
                // this object has been cloned earlier, return reference to that clone
                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);
                cloned[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)
                {

                    if (sourceField.PropertyType.IsArray && piTarget.PropertyType.IsArray)
                    {
                        CopyArray(source, target, piTarget, sourceField, cloned);
                    }
                    else if ((sourceField.PropertyType.IsGenericType && sourceField.PropertyType.GetGenericTypeDefinition() == typeof(List<>))
                             && (piTarget.PropertyType.IsGenericType && piTarget.PropertyType.GetGenericTypeDefinition() == typeof(List<>)))
                    {
                        CopyList(source, target, piTarget, sourceField, cloned);
                    }
                    else
                    {
                        CopySingleData(source, target, piTarget, sourceField, cloned);
                    }
                }
            }
        }
    }

    private static void CopySingleData(object source, object target, PropertyInfo piTarget, PropertyInfo sourceField, Dictionary<object, object> cloned)
    {
        object sourceValue = sourceField.GetValue(source, null);
        object targetValue = piTarget.GetValue(target, null);
        if (sourceValue == null) { return; }

        //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, cloned);
            piTarget.SetValue(target, targetValue, null);
        }
        else
        {
            if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName)
            {
                object tempSourceValue = sourceField.GetValue(source, null);
                piTarget.SetValue(target, tempSourceValue, null);
                cloned[target] = tempSourceValue;
            }
            else
            {
                CopyObjectData(piTarget, target, cloned);
            }
        }
    }

    private static void CopyArray(object source, object target, PropertyInfo piTarget, PropertyInfo sourceField, Dictionary<object, object> cloned)
    {
        object sourceValue = sourceField.GetValue(source, null);
        if (sourceValue == null) { return; }

        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, cloned);
            targetArray.SetValue(tempTarget, i);
        }
        piTarget.SetValue(target, targetArray, null);
        cloned[target] = targetArray;
    }

    private static void CopyList(object source, object target, PropertyInfo piTarget, PropertyInfo sourceField, Dictionary<object, object> cloned)
    {
        var sourceValue = ((IEnumerable)sourceField.GetValue(source, null)).Cast<object>().ToList();
        if (!sourceValue.Any()) { return; }

        int sourceLength = (int)sourceValue.GetType().GetProperty("Count").GetValue(sourceValue);
        var listType = typeof(List<>);
        Type[] genericArgs = sourceField.PropertyType.GetGenericArguments();
        var concreteType = listType.MakeGenericType(genericArgs);
        var newList = (IList)Activator.CreateInstance(concreteType);

        var obj = (IList)sourceField.GetValue(source, null);
        for (int i = 0; i < sourceLength; i++)
        {
            object[] ind = { i };
            object o = obj[i];
            object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetGenericArguments()[0]);
            CopyObjectData(o, tempTarget, cloned);
            newList.Insert(i, tempTarget);
        }
        piTarget.SetValue(target, newList, null);
        cloned[target] = newList;
    }

On execution I am getting "System.Reflection.TargetParameterCountException : Parameter count mismatch.". This is mainly because it is trying to get Team value from a list of teams. Can someone help me with how I could fix this.

Note: This utility code is referred from below links:- https://ddkonline.blogspot.com/2010/04/net-deep-copy-between-2-different.html?_sm_au_=iVV8TZZ5L71MRvZf

https://stackoverflow.com/a/2266441/4583547

Kavya Shetty
  • 185
  • 2
  • 14
  • Please fix your code first. Sometimes the public modifier is missing, sometimes you use capital-case and sometimes lower-case. The `modifiedTeam` line won't work, since `parentTeam` is not of string type... And if its only two classes, implement your own cloning method on both and use them. Easier to maintain and surely more performant then reflection. – Michael Nov 17 '17 at 13:22
  • @Micheal I have updated my code above.Parent team holds reference to the Team driver/codriver is in. Also this is just sample that I have pasted in actual the Team object consists of many more members like the Driver. – Kavya Shetty Nov 17 '17 at 13:30
  • I am open to other methods of cloning as well. But the above one was the one that I found was more relevant. Implementation of ICloneable or adding Serializable tag to my classes is not an option as this is a legacy system. – Kavya Shetty Nov 17 '17 at 13:32
  • The code still won't compile. Your `Team` class doesn't have a `Driver` property, for example. Please take the time to provide a genuine [mcve]. – Jon Skeet Nov 17 '17 at 13:40
  • 1
    You have circular references: A Team has two drivers and a Driver has a parent Team. The reflection way walks each property and tries to resolve its value. Say we have a Team. The tool copies the value of the TeamID - no problem since its a string. Next it tries to copy the Driver, which is a complex object so the DeepCloneUtility walks into this object and tries to clone this. But in this one there is a reference to the parent, so it starts back there... – Michael Nov 17 '17 at 13:46
  • @JonSkeet The above classes just depict the scenario I am facing. The copy utility is the code that I am using. – Kavya Shetty Nov 17 '17 at 13:47
  • @Micheal To fix the circular reference issue I have added each copied object to "Cloned" array and before copying any new item I am checking this array for references. – Kavya Shetty Nov 17 '17 at 13:49
  • comments from downvoters? – Kavya Shetty Nov 17 '17 at 13:51
  • "The above classes just depict the scenario I am facing." But they don't depict it in a reproducible way. You're inconsistent. Why? Why not produce a [mcve] as has been requested before? (And asking why you're getting downvotes when you haven't addressed previous comments seems a little odd.) – Jon Skeet Nov 17 '17 at 13:53
  • @JonSkeet You have added comment that Team class doesn't have a Driver property but as opposed to this it does have driver property and I have updated the case as well post your request. So I don't see the reason for downvote. – Kavya Shetty Nov 17 '17 at 13:57
  • "but as opposed to this it does have driver property" - yes, but that's the point. C# is case-sensitive. You clearly hadn't tested the *actual code you provided*. If you can't be bothered to post code that actually demonstrates your problem, how can you expected other people to be bothered to help you? You still haven't provided a [mcve] - just bits and pieces of it. Please read https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/ and bear in mind that the purpose of Stack Overflow is to build a repository of high quality questions and answers. – Jon Skeet Nov 17 '17 at 13:58
  • (And I notice that the "correction" you've made is to move away from following .NET code conventions - again, think about what leads to a high quality question. Take the time to care about the people who are potentially going to answer your question.) – Jon Skeet Nov 17 '17 at 13:59

1 Answers1

0

You mentioned that you are open to other forms of cloning, so here's a suggestion.

If you can use Json.NET then you can leverage its serialization to perform a clone operation like so:

public static class Cloner
{
    public static T Clone<T>(T source)
    {
        if (ReferenceEquals(source, null))
            return default(T);

        var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
    }

    class ContractResolver : DefaultContractResolver
    {
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(p => base.CreateProperty(p, memberSerialization))
                .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(f => base.CreateProperty(f, memberSerialization)))
                .ToList();
            props.ForEach(p => { p.Writable = true; p.Readable = true; });
            return props;
        }
    }
}

Test project (console app):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace Demo
{
    public class Team
    {
        public string id { get; set; }
        public Driver driver { get; set; }
        public Driver codriver { get; set; }
    }

    public class Driver
    {
        public Team parentTeam { get; set; }
        public string driverId { get; set; }
    }

    class Program
    {
        static void Main()
        {
            var original = new List<Team>
            {
                new Team {id = "T1", driver = new Driver { driverId = "D2" }, codriver = new Driver { driverId = "C1"} },
                new Team {id = "T2", driver = new Driver { driverId = "D1"} }
            };

            var cloned = Cloner.Clone(original);

            // Change original to prove that clone is not referencing it.

            original[0].codriver.driverId = "changed";

            Console.WriteLine(cloned[0].codriver.driverId); // Should be C1
        }
    }

    public static class Cloner
    {
        public static T Clone<T>(T source)
        {
            if (ReferenceEquals(source, null))
                return default(T);

            var settings = new JsonSerializerSettings { ContractResolver = new ContractResolver() };

            return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source, settings), settings);
        }

        class ContractResolver : DefaultContractResolver
        {
            protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
            {
                var props = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                    .Select(p => base.CreateProperty(p, memberSerialization))
                    .Union(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                        .Select(f => base.CreateProperty(f, memberSerialization)))
                    .ToList();
                props.ForEach(p => { p.Writable = true; p.Readable = true; });
                return props;
            }
        }
    }
}

NOTE

See this answer for the original source of the cloning code above.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • Thanks for the reply. Actually I want to able to provide source as well as taarget since my target object is already created. Also there are some properties that I do not wish to clone specified in "_excludedPropertyNames" – Kavya Shetty Nov 17 '17 at 14:06