Как сделать глубокую копию объекта в .NET (в частности, на C#)?[дубликат]
-
02-07-2019 - |
Вопрос
На этот вопрос уже есть ответ здесь:
- Глубокое клонирование объектов 43 ответа
Мне нужна настоящая глубокая копия.В Java это было легко, но как это сделать в C#?
Решение
Я видел несколько разных подходов к этому, но я использую общий служебный метод как таковой:
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}
Примечания:
- Ваш класс ДОЛЖЕН быть помечен как
[Serializable]
для того, чтобы это работало. Ваш исходный файл должен содержать следующий код:
using System.Runtime.Serialization.Formatters.Binary; using System.IO;
Другие советы
я написал метод расширения глубокой копии объекта, основанный на рекурсивном "MemberwiseClone".Это быстро (в три раза быстрее чем BinaryFormatter), и он работает с любым объектом.Вам не нужен конструктор по умолчанию или сериализуемые атрибуты.
Исходный код:
using System.Collections.Generic;
using System.Reflection;
using System.ArrayExtensions;
namespace System
{
public static class ObjectExtensions
{
private static readonly MethodInfo CloneMethod = typeof(Object).GetMethod("MemberwiseClone", BindingFlags.NonPublic | BindingFlags.Instance);
public static bool IsPrimitive(this Type type)
{
if (type == typeof(String)) return true;
return (type.IsValueType & type.IsPrimitive);
}
public static Object Copy(this Object originalObject)
{
return InternalCopy(originalObject, new Dictionary<Object, Object>(new ReferenceEqualityComparer()));
}
private static Object InternalCopy(Object originalObject, IDictionary<Object, Object> visited)
{
if (originalObject == null) return null;
var typeToReflect = originalObject.GetType();
if (IsPrimitive(typeToReflect)) return originalObject;
if (visited.ContainsKey(originalObject)) return visited[originalObject];
if (typeof(Delegate).IsAssignableFrom(typeToReflect)) return null;
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}
}
visited.Add(originalObject, cloneObject);
CopyFields(originalObject, visited, cloneObject, typeToReflect);
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect);
return cloneObject;
}
private static void RecursiveCopyBaseTypePrivateFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect)
{
if (typeToReflect.BaseType != null)
{
RecursiveCopyBaseTypePrivateFields(originalObject, visited, cloneObject, typeToReflect.BaseType);
CopyFields(originalObject, visited, cloneObject, typeToReflect.BaseType, BindingFlags.Instance | BindingFlags.NonPublic, info => info.IsPrivate);
}
}
private static void CopyFields(object originalObject, IDictionary<object, object> visited, object cloneObject, Type typeToReflect, BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy, Func<FieldInfo, bool> filter = null)
{
foreach (FieldInfo fieldInfo in typeToReflect.GetFields(bindingFlags))
{
if (filter != null && filter(fieldInfo) == false) continue;
if (IsPrimitive(fieldInfo.FieldType)) continue;
var originalFieldValue = fieldInfo.GetValue(originalObject);
var clonedFieldValue = InternalCopy(originalFieldValue, visited);
fieldInfo.SetValue(cloneObject, clonedFieldValue);
}
}
public static T Copy<T>(this T original)
{
return (T)Copy((Object)original);
}
}
public class ReferenceEqualityComparer : EqualityComparer<Object>
{
public override bool Equals(object x, object y)
{
return ReferenceEquals(x, y);
}
public override int GetHashCode(object obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
}
namespace ArrayExtensions
{
public static class ArrayExtensions
{
public static void ForEach(this Array array, Action<Array, int[]> action)
{
if (array.LongLength == 0) return;
ArrayTraverse walker = new ArrayTraverse(array);
do action(array, walker.Position);
while (walker.Step());
}
}
internal class ArrayTraverse
{
public int[] Position;
private int[] maxLengths;
public ArrayTraverse(Array array)
{
maxLengths = new int[array.Rank];
for (int i = 0; i < array.Rank; ++i)
{
maxLengths[i] = array.GetLength(i) - 1;
}
Position = new int[array.Rank];
}
public bool Step()
{
for (int i = 0; i < Position.Length; ++i)
{
if (Position[i] < maxLengths[i])
{
Position[i]++;
for (int j = 0; j < i; j++)
{
Position[j] = 0;
}
return true;
}
}
return false;
}
}
}
}
Опираясь на решение Килхоффера...
В C# 3.0 вы можете создать метод расширения следующим образом:
public static class ExtensionMethods
{
// Deep clone
public static T DeepClone<T>(this T a)
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, a);
stream.Position = 0;
return (T) formatter.Deserialize(stream);
}
}
}
который расширяет любой класс, помеченный как [Serializable] с помощью метода DeepClone.
MyClass copy = obj.DeepClone();
Вы можете использовать Вложенный MemberwiseClone для глубокого копирования..Это почти такая же скорость, как копирование структуры значений, и на порядок быстрее, чем (а) отражение или (б) сериализация (как описано в других ответах на этой странице).
Обратите внимание, что если ты используешь Вложенный MemberwiseClone для глубокой копии., вам придется вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона.Это просто:всего несколько строк, см. демонстрационный код ниже.
Вот выходные данные кода, показывающие относительную разницу в производительности (4,77 секунды для глубоко вложенных MemberwiseCopy по сравнению с39,93 секунды для сериализации).Использование вложенного MemberwiseCopy почти так же быстро, как копирование структуры, а копирование структуры чертовски близко к теоретической максимальной скорости, на которую способен .NET, что, вероятно, довольно близко к скорости того же самого в C или C++ (но будет придется провести некоторые эквивалентные тесты, чтобы проверить это утверждение).
Demo of shallow and deep copy, using classes and MemberwiseClone:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:04.7795670,30000000
Demo of shallow and deep copy, using structs and value copying:
Create Bob
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Clone Bob >> BobsSon
Adjust BobsSon details:
BobsSon.Age=2, BobsSon.Purchase.Description=Toy car
Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:
Bob.Age=30, Bob.Purchase.Description=Lamborghini
Elapsed time: 00:00:01.0875454,30000000
Demo of deep copy, using class and serialize/deserialize:
Elapsed time: 00:00:39.9339425,30000000
Чтобы понять, как выполнить глубокое копирование с помощью MemberwiseCopy, вот демонстрационный проект:
// Nested MemberwiseClone example.
// Added to demo how to deep copy a reference class.
[Serializable] // Not required if using MemberwiseClone, only used for speed comparison using serialization.
public class Person
{
public Person(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
[Serializable] // Not required if using MemberwiseClone
public class PurchaseType
{
public string Description;
public PurchaseType ShallowCopy()
{
return (PurchaseType)this.MemberwiseClone();
}
}
public PurchaseType Purchase = new PurchaseType();
public int Age;
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person ShallowCopy()
{
return (Person)this.MemberwiseClone();
}
// Add this if using nested MemberwiseClone.
// This is a class, which is a reference type, so cloning is more difficult.
public Person DeepCopy()
{
// Clone the root ...
Person other = (Person) this.MemberwiseClone();
// ... then clone the nested class.
other.Purchase = this.Purchase.ShallowCopy();
return other;
}
}
// Added to demo how to copy a value struct (this is easy - a deep copy happens by default)
public struct PersonStruct
{
public PersonStruct(int age, string description)
{
this.Age = age;
this.Purchase.Description = description;
}
public struct PurchaseType
{
public string Description;
}
public PurchaseType Purchase;
public int Age;
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct ShallowCopy()
{
return (PersonStruct)this;
}
// This is a struct, which is a value type, so everything is a clone by default.
public PersonStruct DeepCopy()
{
return (PersonStruct)this;
}
}
// Added only for a speed comparison.
public class MyDeepCopy
{
public static T DeepCopy<T>(T obj)
{
object result = null;
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
result = (T)formatter.Deserialize(ms);
ms.Close();
}
return (T)result;
}
}
Затем вызовите демо из основного:
void MyMain(string[] args)
{
{
Console.Write("Demo of shallow and deep copy, using classes and MemberwiseCopy:\n");
var Bob = new Person(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
{
Console.Write("Demo of shallow and deep copy, using structs:\n");
var Bob = new PersonStruct(30, "Lamborghini");
Console.Write(" Create Bob\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Console.Write(" Clone Bob >> BobsSon\n");
var BobsSon = Bob.DeepCopy();
Console.Write(" Adjust BobsSon details:\n");
BobsSon.Age = 2;
BobsSon.Purchase.Description = "Toy car";
Console.Write(" BobsSon.Age={0}, BobsSon.Purchase.Description={1}\n", BobsSon.Age, BobsSon.Purchase.Description);
Console.Write(" Proof of deep copy: If BobsSon is a true clone, then adjusting BobsSon details will not affect Bob:\n");
Console.Write(" Bob.Age={0}, Bob.Purchase.Description={1}\n", Bob.Age, Bob.Purchase.Description);
Debug.Assert(Bob.Age == 30);
Debug.Assert(Bob.Purchase.Description == "Lamborghini");
var sw = new Stopwatch();
sw.Start();
int total = 0;
for (int i = 0; i < 100000; i++)
{
var n = Bob.DeepCopy();
total += n.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
{
Console.Write("Demo of deep copy, using class and serialize/deserialize:\n");
int total = 0;
var sw = new Stopwatch();
sw.Start();
var Bob = new Person(30, "Lamborghini");
for (int i = 0; i < 100000; i++)
{
var BobsSon = MyDeepCopy.DeepCopy<Person>(Bob);
total += BobsSon.Age;
}
Console.Write(" Elapsed time: {0},{1}\n", sw.Elapsed, total);
}
Console.ReadKey();
}
Опять же, обратите внимание, что если ты используешь Вложенный MemberwiseClone для глубокой копии., вам придется вручную реализовать ShallowCopy для каждого вложенного уровня в классе и DeepCopy, который вызывает все указанные методы ShallowCopy для создания полного клона.Это просто:всего несколько строк, см. демонстрационный код выше.
Обратите внимание: когда дело доходит до клонирования объекта, существует большая разница между «структурой» и «классом»:
- Если у вас есть «структура», это тип значения, поэтому вы можете просто скопировать ее, и содержимое будет клонировано.
- Если у вас есть «класс», это ссылочный тип, поэтому, если вы его копируете, все, что вы делаете, — это копируете указатель на него.Чтобы создать настоящий клон, вам нужно проявить больше творчества и использовать метод, который создает в памяти еще одну копию исходного объекта.
- Неправильное клонирование объектов может привести к очень трудно выявляемым ошибкам.В рабочем коде я обычно использую контрольную сумму, чтобы дважды проверить, что объект был правильно клонирован и не был поврежден другой ссылкой на него.Эту контрольную сумму можно отключить в режиме Release.
- Я считаю этот метод весьма полезным:часто требуется клонировать только части объекта, а не весь объект целиком.Это также важно для любого случая использования, когда вы изменяете объекты, а затем помещаете измененные копии в очередь.
Обновлять
Вероятно, можно использовать отражение для рекурсивного обхода графа объекта и выполнения глубокого копирования.WCF использует этот метод для сериализации объекта, включая всех его дочерних элементов.Хитрость заключается в том, чтобы пометить все дочерние объекты атрибутом, который делает их видимыми.Однако вы можете потерять некоторые преимущества в производительности.
Обновлять
Цитата независимого теста скорости (см. комментарии ниже):
Я запустил свой собственный тест на скорость, используя метод расширения Neil's Serialize/Deserialize, вложенного члена Contango, метод расширения Alex Burtsev на основе отражения и Automapper, по 1 миллион раз каждый.Сериализация-десериализация была самой медленной — 15,7 секунды.Затем появился Automapper, заняв 10,1 секунды.Гораздо быстрее был метод, основанный на отражении, который занял 2,4 секунды.Безусловно, самым быстрым был вложенным членом WembiseClone, занимая 0,1 секунды.Сводится к производительности по сравнению с хлопот добавления кода в каждый класс, чтобы клонировать его.Если производительность не является проблемой, иди с методом Алекса Бертева.— Саймон Тьюси
Я считаю, что подход BinaryFormatter относительно медленный (что стало для меня неожиданностью!).Возможно, вы сможете использовать ProtoBuf .NET для некоторых объектов, если они соответствуют требованиям ProtoBuf.Со страницы начала работы с ProtoBuf (http://code.google.com/p/protobuf-net/wiki/GettingStarted):
Примечания о поддерживаемых типах:
Пользовательские классы, которые:
- Помечены как контракт данных
- Иметь конструктор без параметров
- Для Сильверлайта:являются публичными
- Многие распространенные примитивы и т. д.
- Одинокий массивы измерений:Т[]
- Список<T>/IList<T>
- Словарь<TKey, TValue> / IDictionary<TKey, TValue>
- любой тип, реализующий IEnumerable<T> и имеющий метод Add(T)
Код предполагает, что типы будут изменяемыми для избранных членов.Соответственно, пользовательские структуры не поддерживаются, поскольку они должны быть неизменяемыми.
Если ваш класс соответствует этим требованиям, вы можете попробовать:
public static void deepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = Serializer.Deserialize<T>(stream);
}
}
Что действительно ОЧЕНЬ быстро...
Редактировать:
Вот рабочий код для этой модификации (проверено на .NET 4.6).Он использует System.Xml.Serialization и System.IO.Нет необходимости отмечать классы как сериализуемые.
public void DeepCopy<T>(ref T object2Copy, ref T objectCopy)
{
using (var stream = new MemoryStream())
{
var serializer = new XS.XmlSerializer(typeof(T));
serializer.Serialize(stream, object2Copy);
stream.Position = 0;
objectCopy = (T)serializer.Deserialize(stream);
}
}
Вы можете попробовать это
public static object DeepCopy(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(
type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(DeepCopy(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, DeepCopy(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
Спасибо DetoX83 статья в проекте кода.
Возможно, вам нужна только поверхностная копия, в этом случае используйте Object.MemberWiseClone()
.
В документации есть хорошие рекомендации по MemberWiseClone()
для стратегий глубокого копирования:-
http://msdn.microsoft.com/en-us/library/system.object.memberwiseclone.aspx
Лучший способ:
public interface IDeepClonable<T> where T : class
{
T DeepClone();
}
public class MyObj : IDeepClonable<MyObj>
{
public MyObj Clone()
{
var myObj = new MyObj();
myObj._field1 = _field1;//value type
myObj._field2 = _field2;//value type
myObj._field3 = _field3;//value type
if (_child != null)
{
myObj._child = _child.DeepClone(); //reference type .DeepClone() that does the same
}
int len = _array.Length;
myObj._array = new MyObj[len]; // array / collection
for (int i = 0; i < len; i++)
{
myObj._array[i] = _array[i];
}
return myObj;
}
private bool _field1;
public bool Field1
{
get { return _field1; }
set { _field1 = value; }
}
private int _field2;
public int Property2
{
get { return _field2; }
set { _field2 = value; }
}
private string _field3;
public string Property3
{
get { return _field3; }
set { _field3 = value; }
}
private MyObj _child;
private MyObj Child
{
get { return _child; }
set { _child = value; }
}
private MyObj[] _array = new MyObj[4];
}
Документация MSDN, кажется, намекает, что Clone должен выполнить глубокое копирование, но это никогда не указывается явно:
Интерфейс ICloneable содержит один элемент, Clone, который предназначен для поддержки клонирования помимо того, что предоставляется MemberWiseClone… Метод MemberwiseClone создает неглубокую копию…
Вы можете найти мой пост полезным.
public static object CopyObject(object input)
{
if (input != null)
{
object result = Activator.CreateInstance(input.GetType());
foreach (FieldInfo field in input.GetType().GetFields(Consts.AppConsts.FullBindingList))
{
if (field.FieldType.GetInterface("IList", false) == null)
{
field.SetValue(result, field.GetValue(input));
}
else
{
IList listObject = (IList)field.GetValue(result);
if (listObject != null)
{
foreach (object item in ((IList)field.GetValue(input)))
{
listObject.Add(CopyObject(item));
}
}
}
}
return result;
}
else
{
return null;
}
}
Этот способ в несколько раз быстрее, чем BinarySerialization
И для этого не требуется [Serializable]
атрибут.
У меня есть более простая идея.Используйте LINQ с новым выбором.
public class Fruit
{
public string Name {get; set;}
public int SeedCount {get; set;}
}
void SomeMethod()
{
List<Fruit> originalFruits = new List<Fruit>();
originalFruits.Add(new Fruit {Name="Apple", SeedCount=10});
originalFruits.Add(new Fruit {Name="Banana", SeedCount=0});
//Deep Copy
List<Fruit> deepCopiedFruits = from f in originalFruits
select new Fruit {Name=f.Name, SeedCount=f.SeedCount};
}