Глубокое клонирование объекта

Я использую функцию из проекта кода для глубокого клонирования моих объектов

http://www.codeproject.com/KB/tips/SerializedObjectCloner.aspx?msg=3984032

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

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

Спасибо,

Джозеф

4 ответа

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

  1. Используйте генерацию кода, например T4, для генерации кода, который клонирует ваши графы объектов. T4 является частью Visual Studio 2008 и Visual Studio 2010, и у Олега Сича есть отличная документация по T4: http://www.olegsych.com/2007/12/text-template-transformation-toolkit/

  2. Используйте System.Linq.Expression, чтобы во время выполнения генерировать делегатов, которые клонируют ваш объект. Вообще отражение медленное из-за GetValue/SetValue. Однако System.Linq.Expression позволяет генерировать методы из рефлексии, которые жестко закодированы для ваших классов. Эти методы вы затем кешируете и платите за отражение только один раз.

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

Вещи, которые усложняют жизнь для глубокого клонирования:

  1. Интерфейсные поля
  2. Поля абстрактного класса
  3. Классы с закрытыми конструкторами (для получения справки см. http://msdn.microsoft.com/nb-no/library/system.runtime.serialization.formatterservices.getuninitializedobject.aspx)
  4. Коллекционные поля

Написание полноценного глубокого клонера немного сложновато, но, поскольку вы знаете свой домен, вы можете внести некоторые упрощения в проблему.

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).

Другие вопросы по тегам