Почему клонирование (в .NET) так сложно? - PullRequest
19 голосов
/ 21 августа 2009

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

Почему клонирование не так просто, как дублирование блока памяти, в котором расположен объект, и, таким образом, имеет метод Clone в классе object, при котором все классы в .NET наследуют его?

Ответы [ 5 ]

24 голосов
/ 21 августа 2009

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

Если вы хотите реализовать именно эту функциональность, это просто - для этого и нужен Object.MemberwiseClone(). В большинстве случаев, если имеет смысл смысл клонировать объект (что означает клонированный NetworkStream?), Имеет смысл клонировать каждое свойство ... если только оно уже не ссылается на неизменное значение, и т. д. Другими словами, это естественно сложная проблема, поэтому большинство типов не поддерживает клонирование.

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

7 голосов
/ 21 августа 2009

Другие уже объяснили о MemberwiseClone, но никто не дал объяснения , почему это защищено. Я постараюсь дать обоснование.

Проблема в том, что MemberwiseClone просто слепо копирует состояние. Во многих случаях это нежелательно. Например, объект может иметь закрытое поле, которое является ссылкой на List. Мелкая копия, такая как то, что делает MemberwiseClone, приведет к тому, что новый объект будет указывать на тот же список - и класс вполне может быть записан, не ожидая, что список будет предоставлен кому-либо еще.

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

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

Короче говоря, "клонирование" не является четко определенной операцией для произвольных объектов. Тот факт, что для всех классов по умолчанию в C ++ предоставляется operator= по умолчанию, является более неприятным, поскольку слишком часто люди забывают, что он есть, и не отключают его для классов, для которых копирование не имеет смысла или является опасно (а таких классов на удивление много).

4 голосов
/ 21 августа 2009

Существует (как минимум) два вида клонирования. Большинство ссылок говорят о мелком и глубоком клонировании, но в действительности между ними есть оттенки.

Ключевая проблема заключается в противоречии между тем, «сколько нужно скопировать» и «сколько нужно разделить».

Рассмотрим объект Order, содержащий ссылки на Customer, Address и List из OrderLines.

Если вы хотели Clone() и Order, в чем дело?

" Простое копирование блока памяти " даст вам новый Order, но тот, который разделяет Customer, Address и тот же List из OrderLines. (Помните, что члены объекта хранятся по ссылке, поэтому при дублировании блока памяти вы получите две ссылки на один и тот же объект).

Очевидно, вы не хотите делить List из OrderLines между двумя Order с. На самом деле, вы, вероятно, также хотите клонировать каждый OrderLine.

Если бы вы работали с методом общего назначения Clone(), как этот метод мог бы узнать, какие члены должны быть рекурсивно клонированы, а какие нет?

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

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

2 голосов
/ 21 августа 2009

Существует такая вещь, как Object.MemberwiseClone (), , которая делает то, что вы описываете. Это делает мелкую копию объекта.

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

0 голосов
/ 21 августа 2009

Вы должны явно реализовать интерфейс ICloneable в ваших классах.

Однако, как указано в документации, механизм клонирования в MemberwiseClone не различает мелкую и глубокую копию.

...