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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/569154/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 08:35:17  来源:igfitidea点击:

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

c#.netreflectionserialization

提问by Jon Simpson

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.

我需要按字段名称映射 ObjectV1 和 ObjectV2 之间的所有字段值和子集合。ObjectV2 与 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.

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

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

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

采纳答案by Chris Ballard

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.

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

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

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

回答by ?yvind Skaar

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

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

回答by Marc Gravell

What version of .NET is it?

.NET 是什么版本?

For shallow copy:

对于浅拷贝:

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

在 3.5 中,您可以预编译 anExpression来执行此操作。在 2.0 中,您可以HyperDescriptor非常轻松地使用它来做同样的事情。两者都将大大优于反射。

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

还有就是一个预执行罐头Expression中的做法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 ifall the contract-names match, but the two (shallow) options above would be much quicker if they are possible.

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

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

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

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)

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

回答by Simon Steele

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.

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

回答by Alex Shirshov

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

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

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.

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

回答by Th 00 m? s

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

回答by Chris Pitman

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.

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