Реализация интерфейса ICloneable C# (глубокое клонирование)

Я смотрел код, реализующий интерфейс ICloneable для одного из классов.

Класс был следующим:

public class TempClass
{
  String[] names;
  String[] values;
}

Был создан частичный класс, реализующий TempClass

public partial class TempClass:ICloneable
{
   public Object Clone()
   {
      TempClass cloneClass = new TempClass();
      String[] cloneNames = new String[this.names.Length - 1];
      String[] cloneValues = new String[this.values.Length -1];

      Array.Copy(this.names,cloneNames,this.names.Length);
      Array.Copy(this.values,cloneValues,this.values.Length);

      cloneClass.names = cloneNames;
      cloneValues.values = cloneValues;

      return cloneClass;
   }
}

Мне было интересно, будет ли это допустимым способом сделать глубокую копию объекта? Здесь вызывают тревогу промежуточные структуры cloneNames и cloneValues, которые используются для копирования значений исходного объекта и имеют переменные-члены Names и Values, указывающие на него, а затем возвращают ссылку на объект, созданную в методе клонирования.

Любые отзывы об этом фрагменте будут высоко оценены

Спасибо


person sc_ray    schedule 09.11.2009    source источник


Ответы (3)


Array.Clone и Array.Copy создают поверхностные копии (как описано в связанной документации по API), но с использованием строки массивы немного затуманивают проблему, потому что строки неизменяемы в .NET. Когда вы делаете это:

stringArray[0] = "first value";
stringArray[0] = "second value";

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

Чтобы выполнить глубокую копию объекта с массивами обычных ссылочных типов, вам необходимо создать новые экземпляры элементов массива. Скажем, TempClass сохранил массив объектов Cookie вместо строки:

public Object Clone() {
    TempClass clone = new TempClass();

    clone.cookieArray = new Cookie[this.cookieArray.Length];
    for(int i = 0; i < this.cookieArray.Length; i++) {
        Cookie cookie  = this.cookieArray[i];
        clone.cookieArray[i] = new Cookie(cookie.Name, cookie.Value, cookie.Path, cookie.Domain);
    }
}
person Jeff Sternal    schedule 09.11.2009
comment
Хотя это отвечает на вопрос, это недопустимая реализация Clone(). Если это подкласс TempClass, который не повторно реализует Clone(), предлагаемый код не вернет его допустимый клон. Результирующее значение Clone() всегда должно быть получено из вызова MemberwiseClone(). - person Julien; 11.03.2013

Ну, есть одна проблема - вы не создаете новые строковые массивы нужного размера. Должен быть:

String[] cloneNames = new String[this.names.Length];
String[] cloneValues = new String[this.values.Length];

Или как более простое решение:

String[] cloneNames = (String[]) this.names.Clone();
String[] cloneValues = (String[]) this.values.Clone();

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

person Jon Skeet    schedule 09.11.2009
comment
И вдобавок ко всему, почему бы просто не установить массивы-члены клонированного объекта напрямую? - person Jeff Sternal; 09.11.2009
comment
@Jeff: Потому что это был бы поверхностный клон; OP, по-видимому, хочет глубокого клонирования, так что изменение содержимого исходного массива после факта не изменит массивы новых объектов. - person Jon Skeet; 09.11.2009
comment
D'oh, мой вопрос не был ясен - я имею в виду, что добавляют промежуточные переменные? Почему бы и нет cloneClass.names = (String[] this.names.Clone(); - person Jeff Sternal; 09.11.2009
comment
Спасибо, Джефф. Я думал об этом. С точки зрения модели памяти мне было интересно, каковы последствия прямой установки массивов-членов по сравнению с сохранением ссылки на промежуточную структуру. Тони. Разве Array.Clone() не делает поверхностную копию значений в структуре данных? - person sc_ray; 09.11.2009
comment
@sc_ray - это правильно. Однако использование strings делает ситуацию немного запутанной, потому что они неизменяемы. Когда вы «модифицируете» значение элемента string в массиве, вы фактически заменяете его другой ссылкой (как в случае с типом значения). Таким образом, на объект, созданный с помощью TempClass.Clone, не влияют изменения оригинала, но в общем случае это неверно. Например, это неверно для обычных ссылочных типов (скажем, для массива из FileInfo экземпляров). Что бы это ни стоило, Array.Copy также выполняет неглубокую копию. - person Jeff Sternal; 09.11.2009
comment
@Jeff Sternal - Спасибо, Джефф. Итак, как бы мы подошли к этому, если бы у нас был массив объектов вместо строк? - person sc_ray; 09.11.2009
comment
@Jeff: Единственная цель промежуточных переменных здесь заключалась в том, чтобы сохранить код ближе к оригиналу :) - person Jon Skeet; 09.11.2009

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

public Object Clone()
{
    return new TempClass
    {
        names = this.names == null? null:this.names.ToArray(),
        values = this.values == null? null:this.values.ToArray()
    };
}

Это может быть немного медленнее, чем Clone, но это работает.

person Yuriy Faktorovich    schedule 09.11.2009
comment
Спасибо Юрий. Но не будет ли приведенный выше код возвращать ссылку на исходный массив вместо копирования массива и его содержимого в клонированный объект? - person sc_ray; 09.11.2009
comment
Нет, это создаст новый массив. - person Yuriy Faktorovich; 09.11.2009