【问题标题】:How to deep copy between objects of different types in C#.NET如何在 C#.NET 中不同类型的对象之间进行深度复制
【发布时间】:2010-10-08 19:44:53
【问题描述】:

我需要按字段名称映射 ObjectV1 和 ObjectV2 之间的所有字段值和子集合。 ObjectV2 与 ObjectV1 位于不同的命名空间中。

模板 ClassV1 和 ClassV2 之间的继承已被打折,因为这两个类需要独立发展。我考虑过使用反射(很慢)和二进制序列化(也很慢)来执行公共属性的映射。

有首选方法吗?还有其他选择吗?

【问题讨论】:

  • 获取一些有关基于反射的方法的实际指标。您可能会发现现实世界的性能是完全足够的——我做到了!如果没有,请按照 Chris Ballard 的建议...

标签: c# .net reflection serialization


【解决方案1】:

作为每次都使用反射的替代方法,您可以创建一个辅助类,该类使用 Reflection.Emit 动态创建复制方法 - 这意味着您只会在启动时获得性能影响。这可能会为您提供所需的灵活性和性能组合。

由于 Reflection.Emit 相当笨重,我建议查看 this Reflector 插件,它非常适合构建此类代码。

【讨论】:

    【解决方案2】:

    它是什么版本的 .NET?

    对于浅拷贝:

    在 3.5 中,您可以预编译 Expression 来执行此操作。在 2.0 中,您可以很容易地使用 HyperDescriptor 来做同样的事情。两者都将大大优于反射。

    Expression 方法在 MiscUtil - PropertyCopy 中有一个预先固定的实现:

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

    (结束浅)

    BinaryFormatter(在问题中)在这里不是一个选项 - 由于原始类型和目标类型不同,它根本不起作用。如果数据是基于合同的,XmlSerializer 或 DataContractSerializer 将工作如果所有合同名称匹配,但如果可能的话,上面的两个(浅)选项会快得多。

    另外 - 如果你的类型被标记为通用序列化属性(XmlTypeDataContract),那么protobuf-net 可以(在某些情况下)做一个深拷贝/更改类型给你:

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

    但这取决于具有非常相似架构的类型(实际上,它不使用名称,它在属性上使用显式的“顺序”等)

    【讨论】:

    • 您能否提供一些代码示例,说明如何执行预编译的表达式内容和 HyperDescriptor?
    • 我已经为 PropertyCopy 添加了 1-liner,但需要注意(更新):这将执行 shallow 复制。进行深拷贝需要付出一些努力(事实上,深拷贝并不总是可行的)。
    【解决方案3】:

    您可能想看看AutoMapper,这是一个专门用于在对象之间复制值的库。它使用约定优于配置,因此如果属性确实具有完全相同的名称,它将为您完成几乎所有的工作。

    【讨论】:

    • 我希望我可以 +2 或 +3 这个:)
    【解决方案4】:

    这是我构建的解决方案:

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

    【讨论】:

    • 你好,我有一个关于你有趣的 sn-p 的问题。您将如何管理在空目标中复制对象?如果我尝试这样做,我会得到一个例外。
    【解决方案5】:

    如果速度是一个问题,您可以使反射过程脱机并为通用属性的映射生成代码。您可以在运行时使用轻量级代码生成来执行此操作,也可以通过构建 C# 代码来完全离线进行编译。

    【讨论】:

      【解决方案6】:

      如果您控制目标对象的实例化,请尝试使用 JavaScriptSerializer。它不会吐出任何类型信息。

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

      返回

      {Id: 1, Name: "A"}
      

      从这里应该可以反序列化具有相同属性名称的任何类。

      【讨论】:

        【解决方案7】:

        如果速度是个问题,您应该在方法本身中实现克隆方法。

        【讨论】:

        • 好主意,但这会在我试图避免的类之间引入依赖关系。谢谢。
        • 不一定。您可以将状态克隆为通用格式。这些类只需要知道公共协议。
        【解决方案8】:

        对于深拷贝,我使用了 Newtonsoft 和 create 和通用方法,例如:

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

        我知道这不是什么正统的解决方案,但它对我有用。

        【讨论】:

          猜你喜欢
          • 2019-08-21
          • 2016-05-09
          • 1970-01-01
          • 2013-12-03
          • 2018-10-08
          • 2011-07-25
          • 1970-01-01
          相关资源
          最近更新 更多