簡體   English   中英

如何在C#.NET中深層復制不同類型的對象

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

我需要按字段名稱映射ObjectV1和ObjectV2之間的所有字段值和子集合。 ObjectV2與ObjectV1位於不同的namspace中。

模板ClassV1和ClassV2之間的繼承已被打折扣,因為這兩個類需要獨立發展。 我已經考慮過使用反射(慢速)和二進制序列化(也很慢)來執行公共屬性的映射。

有首選方法嗎? 還有其他選擇嗎?

作為每次使用反射的替代方法,您可以創建一個使用Reflection.Emit動態創建復制方法的輔助類 - 這意味着您只能在啟動時獲得性能。 這可以為您提供所需的靈活性和性能組合。

由於Reflection.Emit非常笨重,我建議檢查一下這個 Reflector插件,這對構建這種代碼非常有用。

這是什么版本的.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);

但這取決於具有非常相似模式的類型(實際上,它不使用名稱,它在屬性上使用顯式“順序”等)

您可能需要查看AutoMapper ,這是一個專門在對象之間復制值的庫。 它使用約定優於配置,因此如果屬性確實具有exaxt相同的名稱,它將為您完成幾乎所有的工作。

這是我建立的解決方案:

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

如果您控制目標對象的實例化,請嘗試使用JavaScriptSerializer 它不會吐出任何類型的信息。

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

回報

{Id: 1, Name: "A"}

從這里可以反序列化具有相同屬性名稱的任何類。

如果速度是一個問題,您可以使反射過程脫機並生成用於映射公共屬性的代碼。 您可以使用輕量級代碼生成在運行時執行此操作,也可以通過構建要編譯的C#代碼完全脫機。

如果速度是一個問題,您應該在方法本身中實現克隆方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM