c#深层复制反射GetMembers

时间:2017-11-17 13:03:55

标签: c# list reflection deep-copy

我正在尝试深层复制复杂对象

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"} }
};

//注意:父团队持有对驱动程序或codriver所在团队的引用

我想深度复制修改后的列表。以下是我正在使用的实用程序。

    /// <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;
    }

执行时我收到“System.Reflection.TargetParameterCountException:Parameter count mismatch。”。这主要是因为它试图从团队列表中获取团队价值。有人可以帮我解决这个问题。

注意:此实用程序代码参考以下链接: - https://ddkonline.blogspot.com/2010/04/net-deep-copy-between-2-different.html?_sm_au_=iVV8TZZ5L71MRvZf

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

1 个答案:

答案 0 :(得分:0)

你提到你对其他形式的克隆持开放态度,所以这里有一个建议。

如果您可以使用Json.NET,那么您可以利用其序列化来执行克隆操作,如下所示:

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;
        }
    }
}

测试项目(控制台应用):

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;
            }
        }
    }
}

注意

有关上述克隆代码的原始来源,请参阅this answer