如何在C#.NET中实现不同类型对象之间的深度复制

10

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

由于这两个类需要独立发展,因此排除了模板ClassV1和ClassV2之间的继承关系。我考虑使用反射(速度较慢)和二进制序列化(也很慢)来执行公共属性的映射。

有没有更好的方法?还有其他选择吗?


2
获取反射方法的实际度量指标。你可能会发现,实际世界的性能是完全足够的 - 我就是这样!如果不行,可以按照Chris Ballard建议的方式去做... - kpollock
9个回答

6
作为替代使用反射的方法,您可以创建一个帮助类,使用Reflection.Emit动态创建复制方法 - 这意味着您只会在启动时受到性能影响。这可能会给您所需的灵活性和性能的组合。
由于Reflection.Emit相当笨重,我建议查看此Reflector addin,它非常适合构建此类代码:this.

4

这是哪个版本的.NET?

浅拷贝:

在3.5中,您可以预编译一个Expression来完成此操作。在2.0中,您可以轻松使用HyperDescriptor来完成相同的操作。两者都比反射大大提高了性能。

MiscUtil中有一个预先制作的Expression方法 - PropertyCopy,可以直接使用:

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

(浅复制结束)

在这种情况下,BinaryFormatter(在问题中提到)不是一个选项 - 它根本不起作用,因为原始类型和目标类型不同。如果数据是基于合同的,则 XmlSerializer 或 DataContractSerializer 将工作如果所有合同名称匹配,但如果可能,上面两个(浅层)选项会更快。

此外 - 如果您的类型带有常见序列化属性(XmlTypeDataContract),那么protobuf-net可以(在某些情况下)为您执行深度复制/更改类型:

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

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


你能提供一些关于如何使用预编译表达式和HyperDescriptor的代码示例吗? - Svish
我已经为PropertyCopy添加了一行代码,但是需要注意的是(更新):这将执行浅复制。要进行深层复制需要一些努力(实际上,深层复制并不总是可能的)。 - Marc Gravell

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

我希望我能够给这个点赞 +2 或 +3 :) - jao

2

以下是我制作的解决方案:

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

嗨,我有一个关于你有趣的代码片段的问题。如果我试图在一个空目标中复制一个对象,你会如何处理?我尝试过但是出现了异常。 - Andy M

1
如果您可以控制目标对象的实例化,请尝试使用JavaScriptSerializer。它不会输出任何类型信息。
new JavaScriptSerializer().Serialize(new NamespaceA.Person{Id = 1, Name = "A"})

返回

{Id: 1, Name: "A"}

通过这个,我们可以反序列化任何具有相同属性名称的类。


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

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

好主意,但这会在我试图避免的类之间引入依赖关系。谢谢。 - Jon Simpson
不一定。您可以将状态克隆到通用格式中,类只需了解公共协议即可。 - Øyvind Skaar

0

为了进行深度复制,我使用了Newtonsoft并创建了一个通用方法,例如:

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

我知道这不是很正统的解决方案,但它对我很有效。


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

你应该添加解释。 - Super Kai - Kazuya Ito
谢谢您为Stack Overflow社区做出的贡献。这可能是一个正确的答案,但如果您能提供代码的额外解释,让开发人员能够理解您的推理,那将非常有用。对于不太熟悉语法或难以理解概念的新开发人员来说,这尤其有帮助。您是否可以编辑您的答案,并包含额外细节,以造福社区? - Jeremy Caney

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接