Эффективное клонирование кэшированных объектов

StackOverflow https://stackoverflow.com/questions/800031

Вопрос

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

Вот рабочий процесс:

  1. Элемент данных 1 - это текущий элемент в памяти.Этот элемент был первоначально извлечен из кэша и глубоко клонирован (все вложенные объекты, такие как словари и т.д.).Затем элемент данных 1 редактируется, и его свойства изменяются.
  2. Затем мы сравниваем этот объект с исходной версией, которая была сохранена в кэше.Поскольку элемент данных 1 был клонирован и его свойства изменены, эти объекты должны отличаться.

Здесь есть пара проблем.

Основная проблема заключается в том, что наш метод глубокого клонирования очень дорогой.Мы сравнили его с неглубоким клоном, и он был в 10 раз медленнее.Это чушь собачья.Вот наш метод глубокого клонирования:

    public object Clone()    
    {
        using (var memStream = new MemoryStream())
        {
            var binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
            binaryFormatter.Serialize(memStream, this); 
            memStream.Seek(0, SeekOrigin.Begin);
            return binaryFormatter.Deserialize(memStream);
        }
    }

Изначально мы использовали следующее для клонирования:

public object Clone()
{
    return this.MemberwiseClone();
}

Это было более производительно, но поскольку оно выполняет неглубокий клонирование, все сложные объекты, которые были свойствами этого объекта, такие как Словари и т.д., Не были клонированы.Объект по-прежнему будет содержать ту же ссылку, что и объект, который был в кэше, поэтому свойства будут теми же при сравнении.

Итак, есть ли у кого-нибудь эффективный способ глубокого клонирования объектов C #, который охватывал бы клонирование всего графа объектов?

Это было полезно?

Решение

Вы не сможете добиться большего, чем ваша обычная двоичная сериализация, без явной реализации ICloneable для всех ваших объектов данных, которые необходимо клонировать.Другой возможный путь - рефлексия, но и она вас не устроит, если вы ищете производительность.

Я бы подумал о том, чтобы использовать ICloneable для глубокого копирования и / или IComparable для сравнения, если объекты разные...если производительность является для вас такой большой проблемой.

Другие советы

Может быть, тогда вам не стоит глубоко клонировать?

Другие варианты:

1) Сделайте так, чтобы ваш "кэшированный" объект запомнил свое исходное состояние и сделал IT обновляйте флаг "изменено" каждый раз, когда что-либо меняется.

2) Не запоминайте исходное состояние и просто помечайте объект как грязный, как только что-либо изменится.Затем перезагрузите объект из исходного источника для сравнения.Держу пари, ваши объекты меняются реже, чем не меняются, и еще реже возвращаются к тому же значению.

Возможно, что мой ответ может не относиться к вашему случаю, потому что я не знаю, каковы ваши ограничения и требования, но мне кажется, что клонирование общего назначения может быть проблематичным.Как вы уже столкнулись, производительность может быть проблемой.Что-то должно идентифицировать уникальные экземпляры в графе объектов, а затем создать точную копию.Это то, что двоичный сериализатор делает для вас, но он также делает больше (саму сериализацию).Я не удивлен, увидев, что это происходит медленнее, чем вы ожидали.У меня есть аналогичный опыт (кстати, также связанный с кэшированием).Мой подход состоял бы в том, чтобы реализовать клонирование самостоятельно;т. е.реализуйте IClonnable для классов, которые на самом деле необходимо клонировать.Сколько классов существует в вашем приложении, которые вы кэшируете?Если их слишком много (для ручного кодирования клонирования), имеет ли смысл рассмотреть возможность некоторой генерации кода?

Вы можете произвести глубокое клонирование двумя способами:Посредством реализации ICloneable (и вызова метода Object.MemberwiseClone) или посредством двоичной сериализации.

Первый Способ

Первый (и, вероятно, более быстрый, но не всегда лучший) способ - реализовать ICloneable интерфейс в каждом типе.Приведенный ниже пример иллюстрирует это.Класс C реализует ICloneable, и поскольку этот класс ссылается на другие классы D и E, то последние также реализуют этот интерфейс.Внутри метода Clone в C мы вызываем метод Clone других типов.

Public Class C
Implements ICloneable

    Dim a As Integer
    ' Reference-type fields:
    Dim d As D
    Dim e As E

    Private Function Clone() As Object Implements System.ICloneable.Clone
        ' Shallow copy:
        Dim copy As C = CType(Me.MemberwiseClone, C)
        ' Deep copy: Copy the reference types of this object:
        If copy.d IsNot Nothing Then copy.d = CType(d.Clone, D)
        If copy.e IsNot Nothing Then copy.e = CType(e.Clone, E)
        Return copy
    End Function
End Class

Public Class D
Implements ICloneable

    Public Function Clone() As Object Implements System.ICloneable.Clone
        Return Me.MemberwiseClone()
    End Function
End Class

Public Class E
Implements ICloneable

    Public Function Clone() As Object Implements System.ICloneable.Clone
        Return Me.MemberwiseClone()
    End Function
End Class

Теперь, когда вы вызываете метод Clone для экземпляра C, вы получаете глубокое клонирование этого экземпляра:

Dim c1 As New C
Dim c2 As C = CType(c1.Clone, C)   ' Deep cloning.  c1 and c2 point to two different 
                                   ' locations in memory, while their values are the 
                                   ' same at the moment.  Changing a value of one of
                                   ' these objects will NOT affect the other.

Примечание:Если классы D и E имеют ссылочные типы, вы должны реализовать их метод Clone, как мы это сделали для класса C.И так далее.

Предупреждения:1-Приведенный выше пример действителен до тех пор, пока нет циклической ссылки.Например, если класс C имеет самоссылку (например, поле типа C), реализовать интерфейс ICloneable будет непросто, поскольку метод Clone в C может входить в бесконечный цикл.

2-Еще одна вещь, которую следует отметить, это то, что метод MemberwiseClone является защищенным методом класса Object.Это означает, что вы можете использовать этот метод только из кода класса, как показано выше.Это означает, что вы не можете использовать его для внешних классов.

Следовательно, реализация ICloneable допустима только тогда, когда два приведенных выше предупреждения не существуют.В противном случае вам следует использовать метод двоичной сериализации.

Второй Способ

Двоичная сериализация может быть использована для глубокого клонирования без проблем, перечисленных выше (особенно циклической ссылки).Вот общий метод, который выполняет глубокое клонирование с использованием двоичной сериализации:

Public Class Cloning
    Public Shared Function DeepClone(Of T)(ByVal obj As T) As T
        Using MStrm As New MemoryStream(100)    ' Create a memory stream.
            ' Create a binary formatter:
            Dim BF As New BinaryFormatter(Nothing, New StreamingContext(StreamingContextStates.Clone))

            BF.Serialize(MStrm, obj)    ' Serialize the object into MStrm.
            ' Seek the beginning of the stream, and then deserialize MStrm:
            MStrm.Seek(0, SeekOrigin.Begin)
            Return CType(BF.Deserialize(MStrm), T)
        End Using
    End Function
End Class

Вот как использовать этот метод:

Dim c1 As New C
Dim c2 As C = Cloning.DeepClone(Of C)(c1)   ' Deep cloning of c1 into c2.  No need to 
                                            ' worry about circular references!
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top