Как глубоко копировать между объектами разных типов в C # .NET - PullRequest
10 голосов
/ 20 февраля 2009

У меня есть требование отобразить все значения полей и дочерние коллекции между ObjectV1 и ObjectV2 по имени поля. ObjectV2 находится в другом пространстве, чем ObjectV1.

Наследование между шаблоном ClassV1 и ClassV2 не учитывается, так как эти два класса должны развиваться независимо. Я рассмотрел использование как отражения (что является медленным), так и двоичной сериализации (который также является медленным) для выполнения сопоставления общих свойств.

Есть ли предпочтительный подход? Есть ли другие альтернативы?

Ответы [ 7 ]

6 голосов
/ 20 февраля 2009

В качестве альтернативы использованию отражения каждый раз, вы можете создать вспомогательный класс, который динамически создает методы копирования с использованием Reflection.Emit - это будет означать, что вы получите только снижение производительности при запуске. Это может дать вам необходимую вам гибкость и производительность.

Поскольку Reflection.Emit довольно неуклюжий, я бы посоветовал проверить этот Reflector addin, который отлично подходит для создания такого кода.

4 голосов
/ 20 февраля 2009

Какая версия .NET это?

Для мелкой копии:

В 3.5 вы можете предварительно скомпилировать Expression, чтобы сделать это. В 2.0 вы можете использовать HyperDescriptor очень легко, чтобы сделать то же самое. И то, и другое значительно превзойдет результат.

В MiscUtil - PropertyCopy:

существует заранее запланированная реализация подхода Expression.
DestType clone = PropertyCopy<DestType>.CopyFrom(original);

(мелкий конец)

BinaryFormatter (в вопросе) здесь не вариант - он просто не будет работать, так как исходный и целевой типы различаются. Если данные основаны на контракте, XmlSerializer или DataContractSerializer будут работать , если совпадают все имена контрактов, но два (поверхностных) варианта выше будут намного быстрее, если они возможны.

Также - если ваши типы помечены общими атрибутами сериализации (XmlType или DataContract), то protobuf-net может (в некоторых случаях) выполнить глубокое тип копии / изменения для вас:

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

Но это зависит от типов, имеющих очень похожие схемы (на самом деле, он не использует имена, он использует явный «Порядок» и т. Д. Для атрибутов)

3 голосов
/ 15 февраля 2010

Возможно, вы захотите взглянуть на AutoMapper , библиотеку, которая специализируется на копировании значений между объектами. Он использует соглашение о конфигурации, поэтому, если свойства действительно имеют одинаковые имена, он сделает почти всю работу за вас.

2 голосов
/ 15 февраля 2010

Вот решение, которое я построил:

     /// <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);
        }
1 голос
/ 15 февраля 2010

Если вы управляете созданием объекта назначения, попробуйте использовать JavaScriptSerializer . Он не выдает никакой информации о типах.

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

возвращает

{Id: 1, Name: "A"}

Отсюда следует десериализовать любой класс с такими же именами свойств.

1 голос
/ 20 февраля 2009

Если скорость является проблемой, вы можете перевести процесс отражения в автономный режим и сгенерировать код для отображения общих свойств. Вы можете сделать это во время выполнения, используя легковесную генерацию кода или полностью в автономном режиме, создав код C # для компиляции.

0 голосов
/ 20 февраля 2009

Если скорость является проблемой, вы должны реализовать методы клонирования в самих методах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...