Глубокое клонирование объекта
Я использую функцию из проекта кода для глубокого клонирования моих объектов
http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx?msg=3984032
однако для чувствительных к производительности приложений у меня есть снижение производительности, которое составляет приблизительно 10% времени выполнения.
Кто-нибудь может предложить мне другой способ создания копии объекта и снижения производительности? Мой объект довольно большой и содержит списки объектов, которые в свою очередь содержат список объектов и т. Д.
Спасибо,
Джозеф
4 ответа
Я могу предложить несколько подходов, но они не обязательно очень просты в реализации. Два подхода, которые я лично выбрал бы для этого:
Используйте генерацию кода, например T4, для генерации кода, который клонирует ваши графы объектов. T4 является частью Visual Studio 2008 и Visual Studio 2010, и у Олега Сича есть отличная документация по T4: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
Используйте System.Linq.Expression, чтобы во время выполнения генерировать делегатов, которые клонируют ваш объект. Вообще отражение медленное из-за GetValue/SetValue. Однако System.Linq.Expression позволяет генерировать методы из рефлексии, которые жестко закодированы для ваших классов. Эти методы вы затем кешируете и платите за отражение только один раз.
Оба этих подхода должны дать вашу производительность, сравнимую с тем, если бы вы вручную кодировали логику глубокого клонирования.
Вещи, которые усложняют жизнь для глубокого клонирования:
- Интерфейсные поля
- Поля абстрактного класса
- Классы с закрытыми конструкторами (для получения справки см. http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx)
- Коллекционные поля
Написание полноценного глубокого клонера немного сложновато, но, поскольку вы знаете свой домен, вы можете внести некоторые упрощения в проблему.
PS. Лично я предпочитаю T4, а не System.Linq.Expression, поскольку это менее "волшебно"
Вы можете использовать отражение, чтобы получить все приватные поля объекта. Создайте функцию для обхода приватных полей объекта. Возьмите любые типы значений и скопируйте значение. Если объект поддерживает интерфейс ICloneable, вызовите это. Рекурсивный вызов этой функции-клона для ссылочных типов в классе.
Редактировать, вот код для этого: я думаю, что я получил CloneDictionary где-то в Интернете, но я не помню, где сейчас. Кроме того, я только что преобразовал это из VB.net в C#.
public static object GenericClone(object Obj)
{
object Out = null;
Out = Activator.CreateInstance(Obj.GetType());
Type mytype = Obj.GetType();
while (mytype != null) {
foreach (System.Reflection.FieldInfo item in mytype.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)) {
object itemValue = item.GetValue(Obj);
object newvalue = null;
if (itemValue != null) {
if (typeof(System.ICloneable).IsAssignableFrom(itemValue.GetType())) {
newvalue = ((System.ICloneable)itemValue).Clone();
} else {
if (itemValue.GetType().IsValueType) {
newvalue = itemValue;
} else {
if (itemValue.GetType().Name == "Dictionary`2") {
newvalue = DataInterface.CloneDictionary(itemValue);
} else if (object.ReferenceEquals(itemValue.GetType(), typeof(System.Text.StringBuilder))) {
newvalue = new System.Text.StringBuilder(((System.Text.StringBuilder)itemValue).ToString());
} else if (itemValue.GetType().Name == "List`1") {
newvalue = DataInterface.CloneList(itemValue);
} else {
throw (new Exception(item.Name + ", member of " + mytype.Name + " is not cloneable or of value type."));
}
}
}
}
//set new obj copied data
mytype.GetField(item.Name, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance).SetValue(Out, newvalue);
}
//must move up to base type, GetFields does not return inherited fields
mytype = mytype.BaseType;
}
return Out;
}
public static Dictionary<K, V> CloneDictionary<K, V>(Dictionary<K, V> dict)
{
Dictionary<K, V> newDict = null;
// The clone method is immune to the source dictionary being null.
if (dict != null) {
// If the key and value are value types, clone without serialization.
if (((typeof(K).IsValueType || object.ReferenceEquals(typeof(K), typeof(string))) && (typeof(V).IsValueType) || object.ReferenceEquals(typeof(V), typeof(string)))) {
newDict = new Dictionary<K, V>();
// Clone by copying the value types.
foreach (KeyValuePair<K, V> kvp in dict) {
newDict[kvp.Key] = kvp.Value;
}
} else {
newDict = new Dictionary<K, V>();
// Clone by copying the value types.
foreach (KeyValuePair<K, V> kvp in dict) {
newDict[kvp.Key] = DataInterface.GenericClone(kvp.Value);
}
}
}
return newDict;
}
public static List<T> CloneList<T>(List<T> list)
{
List<T> Out = new List<T>();
if (typeof(System.ICloneable).IsAssignableFrom(typeof(T))) {
return (from x in list(T)((ICloneable)x).Clone()).ToList;
} else if (typeof(T).IsValueType) {
return (from x in list(T)x).ToList;
} else {
throw new InvalidOperationException("List elements not of value or cloneable type.");
}
}
Если вы можете немного покрасить свой объектный граф, вы можете использовать protobuf-net. (Вы можете получить его, например, с помощью nuget)
Тривиальный пример:
[Serializable]
[ProtoContract]
public class TestObject
{
[ProtoMember(1)]
public string TestProperty { get; set; }
}
public static class ObjectCopier
{
/// <summary>
/// Perform a deep Copy of the object.
/// </summary>
/// <typeparam name="T">The type of object being copied.</typeparam>
/// <param name="source">The object instance to copy.</param>
/// <returns>The copied object.</returns>
public static T Clone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
Stream stream = new MemoryStream();
using (stream)
{
Serializer.Serialize<T>(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
}
}
}
Примечание: Serializer на самом деле имеет метод DeepClone, который кажется подходящим для этого, но я обнаружил, что он медленнее, чем выполнение Serialize с последующей десериализацией.
ОБНОВЛЕНИЕ: Что касается вопроса Марка, это кажется очень странным. Это мой (очень ограниченный) тест, который на 30% медленнее при использовании глубокого клонирования. (Примечание. Даже если тесты выполняются в другом порядке, а не в параллельном)
[TestMethod]
public void TestWithStream()
{
var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < objects.Count; ++i)
{
ObjectCopier.CloneWithStream(objects[i]);
}
Console.WriteLine(w.Elapsed);
}
[TestMethod]
public void TestWithDeepClone()
{
var objects = Enumerable.Range(0, 1000000).Select(_ => new TestObject { TestProperty = Guid.NewGuid().ToString() }).ToList();
Stopwatch w = Stopwatch.StartNew();
for (int i = 0; i < objects.Count; ++i)
{
ObjectCopier.CloneWithDeepClone(objects[i]);
}
Console.WriteLine(w.Elapsed);
}
public static class ObjectCopier
{
public static T CloneWithStream<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
Stream stream = new MemoryStream();
using (stream)
{
Serializer.Serialize<T>(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return Serializer.Deserialize<T>(stream);
}
}
public static T CloneWithDeepClone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
return Serializer.DeepClone(source);
}
}
Создание глубокой копии общего объекта clr невозможно без использования общего сериализатора (такого как BinaryFormatter) или реализации ручного копирования для всей вашей иерархии. Если BinaryFormatter слишком медленный, вы должны либо вернуться к ручной сериализации, либо найти / реализовать более быстрый форматировщик. Обратите внимание, что большинство реализаций protobuf не будут работать из коробки с общими графами объектов (сериализация делегатов, синглетонов, нулевых коллекций, ...). Поэтому сначала выясните, допускает ли ваш граф сериализацию protobuf, потенциально вы можете сериализовать с помощью BinaryFormatter и, по возможности, использовать protobufs или ручную бинарную запись для определенных подграфов (хранящихся с использованием ISerializable).