Создайте глубокую копию в С#
Я хочу сделать глубокую копию объекта, чтобы я мог изменить новую копию и по-прежнему иметь возможность отменить мои изменения и вернуть исходный объект.
Моя проблема здесь в том, что объект может быть любого типа, даже из неизвестной сборки.
Я не могу использовать 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 много!
Ответы
Ответ 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. Вы должны сказать классу, как клонировать себя. С# не различает агрегацию и состав и использует ссылки на объекты для обоих.
Если у вас есть класс для хранения информации о машине, у него могут быть экземпляры полей, таких как двигатель, колеса и т.д. (состав), но также и производитель (агрегация). Оба сохраняются в качестве ссылок.
Если вы клонировали автомобиль, вы ожидаете, что двигатель и колеса тоже будут клонированы, но вы, разумеется, не захотите клонировать также производителя.