Подтвердить что ты не робот

Создайте глубокую копию в С#

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

Моя проблема здесь в том, что объект может быть любого типа, даже из неизвестной сборки. Я не могу использовать BinaryFormatter или XmlSerializer, потому что объект без необходимости имеет атрибут [Serializable].

Я попытался сделать это с помощью метода Object.MemberwiseClone():

public object DeepCopy(object obj)
{
    var memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

    var newCopy = memberwiseClone.Invoke(obj, new object[0]);

    foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
    {
        if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
        {
            var fieldCopy = DeepCopy(field.GetValue(newCopy));
            field.SetValue(newCopy, fieldCopy);
        }
    }
    return newCopy;
}

Проблема в том, что он не работает над перечислимым (массив, список и т.д.), а не в словаре.

Итак, как я могу сделать глубокую копию неизвестного объекта в С#?

TNX много!

4b9b3361

Ответ 1

Полностью невозможно копировать произвольный объект.

Например, как бы вы обрабатывали Control или FileStream или HttpWebResponse?

Ваш код не может знать, как работает объект и какие его поля должны содержать.

Не делайте этого.
Это рецепт катастрофы.

Ответ 2

Сделать глубокую копию произвольного объекта довольно сложно. Что делать, если объект содержит доступ к ресурсу, например открытый файл с возможностями записи, или сетевое соединение? Не зная, какая информация хранится в объекте, мне было бы сложно сделать копию объекта и работать точно так же. Возможно, вы сможете использовать отражение, но это будет довольно сложно. Для начала вам нужно будет иметь какой-то список, чтобы сохранить все объекты, которые вы скопировали, иначе, если A ссылается на B и B, ссылается на A, вы можете оказаться в бесконечном цикле.

Ответ 3

Соглашаться с SLaks. Вам всегда нужна пользовательская копирующая семантика только для distingush между погодой, которую вы хотите иметь глубокую копию или плоскую копию. (Что такое ссылка, какая содержащая ссылка в смысле составной ссылки.)

Образец, о котором вы говорите, это шаблон памяти.

Прочитайте статью о том, как ее реализовать. Но в основном получается создание настраиваемой копии. Либо внутри класса, либо внешнего в "copy factory".

Ответ 4

Чтобы ответить на ваш вопрос, вы можете глубоко скопировать массив, вызвав Array.CreateInstance и скопировав содержимое массива, вызвав GetValue и SetValue.

Однако вы не должны делать это для произвольных объектов.

Например:

  • Что относительно обработчиков событий?
  • Некоторые объекты имеют уникальные идентификаторы; вы создадите дубликаты идентификаторов.
  • Что относительно объектов, которые ссылаются на их родителей?

Ответ 5

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

Две вещи о вашем оригинальном методе:

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

Обратите внимание также, что существует метод, позволяющий создать произвольный объект класса без вызова любого из его конструкторов. BinaryFormatter использовал его, и он был общедоступным. К сожалению, я не помню, как это называлось и где оно жило. Что-то о времени выполнения или сортировке.

Обновление: System.Runtime.Serialization.FormatterServices.GetUninitializedObject Обратите внимание, что

Вы не можете использовать метод GetUninitializedObject для создания экземпляров типов, которые производятся из класса ContextBoundObject.

Ответ 6

Хорошо, я немного изменил вашу рутину. Вам нужно будет очистить его, но он должен выполнить то, что вы хотите. Я не тестировал это против элементов управления или фильтров, и в этих случаях следует соблюдать осторожность.

Я избегаю член-клонирования для Activator.CreateInstance. Это создаст новые экземпляры ссылочных типов и типов значений копий. Если вы используете объекты с многомерными массивами, вам нужно будет использовать ранжирование массива и итерацию для каждого ранга.

    static object DeepCopyMine(object obj)
    {
        if (obj == null) return null;

        object newCopy;
        if (obj.GetType().IsArray)
        {
            var t = obj.GetType();
            var e = t.GetElementType();
            var r = t.GetArrayRank();
            Array a = (Array)obj;
            newCopy = Array.CreateInstance(e, a.Length);
            Array n = (Array)newCopy;
            for (int i=0; i<a.Length; i++)
            {
                n.SetValue(DeepCopyMine(a.GetValue(i)), i);
            }
            return newCopy;
        }
        else
        {
            newCopy = Activator.CreateInstance(obj.GetType(), true);
        }

        foreach (var field in newCopy.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            if (!field.FieldType.IsPrimitive && field.FieldType != typeof(string))
            {
                var fieldCopy = DeepCopyMine(field.GetValue(obj));
                field.SetValue(newCopy, fieldCopy);
            }
            else
            {
                field.SetValue(newCopy, field.GetValue(obj));
            }
        }
        return newCopy;
    }

Ответ 7

Еще одна причина не копировать произвольные объекты: невозможно узнать, не изучая код объекта, как этот объект будет относиться к другим объектам в системе или какие-то магические значения. Например, два объекта могут содержать ссылки на массивы, которые содержат единственное целое число, равное пяти. Оба этих массива используются в другом месте программы. Какая бы ни была проблема в том, что либо массив имеет значение 5, а скорее эффект, который может записать запись в массив при выполнении другой программы. Нет никакого способа, чтобы сериализатор, автор которого не знает, для чего используются массивы, сможет сохранить эти отношения.

Ответ 8

Вот описание ответа на @schoetbi. Вы должны сказать классу, как клонировать себя. С# не различает агрегацию и состав и использует ссылки на объекты для обоих.

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

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