Копировать конструктор против Clone ()

В C #, каков предпочтительный способ добавления (глубокой) функции копирования в class? Следует ли реализовать конструктор копирования или, скорее, получить из ICloneable и реализовать метод Clone() ?

Замечание : я написал «глубоко» в скобках, потому что думал, что это не имеет значения. По-видимому, другие не согласны, поэтому я спросил, должен ли конструктор / оператор / функция копирования указать, какой вариант копирования он реализует .

Вы не должны извлекаться из ICloneable .

Причина в том, что когда Microsoft разрабатывала инфраструктуру .net, они никогда не указывали, должен ли метод Clone() на ICloneable быть глубоким или мелким клоном, поэтому интерфейс семантически нарушается, так как ваши абоненты не будут знать, будет ли вызов глубоким или мелким клонировать объект.

Вместо этого вы должны определить свои собственные IDeepCloneableIShallowCloneable ) интерфейсы с DeepClone()ShallowClone() ).

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

 public interface IDeepCloneable { object DeepClone(); } public interface IDeepCloneable : IDeepCloneable { T DeepClone(); } 

Что бы вы тогда реализовали следующим образом:

 public class SampleClass : IDeepCloneable { public SampleClass DeepClone() { // Deep clone your object return ...; } object IDeepCloneable.DeepClone() { return this.DeepClone(); } } 

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

Это обсуждается в Руководстве по дизайну .net Framework и в блоге Брэда Абрамса

(Я полагаю, что если вы пишете приложение (в отличие от фреймворка / библиотеки), поэтому вы можете быть уверены, что никто за пределами вашей команды не будет вызывать ваш код, это не имеет большого значения, и вы можете назначить семантический смысл «deepclone» к интерфейсу .net ICloneable, но вы должны убедиться, что это хорошо документировано и хорошо понято внутри вашей команды. Лично я придерживаюсь руководящих принципов.)

В C #, каков предпочтительный способ добавления (глубокой) функции копирования в class? Следует ли реализовать конструктор копирования или, скорее, получить из ICloneable и реализовать метод Clone ()?

Проблема с ICloneable , как уже отмечалось в других ICloneable заключается в том, что она не указывает, является ли она глубокой или мелкой копией, что делает ее практически нецелесообразной и на практике редко используется. Он также возвращает object , который является болью, поскольку он требует много кастинга. (И хотя вы специально упомянули classы в вопросе, реализация ICloneable в struct требует бокса.)

У копии constuctor также есть одна из проблем с ICloneable. Неясно, делает ли конструктор копии глубокую или мелкую копию.

 Account clonedAccount = new Account(currentAccount); // Deep or shallow? 

Лучше всего было бы создать метод DeepClone (). Таким образом, цель совершенно ясна.

Это ставит вопрос о том, должен ли он быть статическим или методом экземпляра.

 Account clonedAccount = currentAccount.DeepClone(); // instance method 

или

 Account clonedAccount = Account.DeepClone(currentAccount); // static method 

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

 class CheckingAccount : Account { CheckAuthorizationScheme checkAuthorizationScheme; public override Account DeepClone() { CheckingAccount clone = new CheckingAccount(); DeepCloneFields(clone); return clone; } protected override void DeepCloneFields(Account clone) { base.DeepCloneFields(clone); ((CheckingAccount)clone).checkAuthorizationScheme = this.checkAuthorizationScheme.DeepClone(); } } 

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

Если вам требуется полиморфное клонирование, вы можете добавить abstract или virtual метод Clone() в ваш базовый class, который вы реализуете, с вызовом конструктора копирования.

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

Пример:

 public class BaseType { readonly int mBaseField; public BaseType(BaseType pSource) { mBaseField = pSource.mBaseField; } public virtual BaseType Clone() { return new BaseType(this); } } public class SubType : BaseType { readonly int mSubField; public SubType(SubType pSource) : base(pSource) { mSubField = pSource.mSubField; } public override BaseType Clone() { return new SubType(this); } } 

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

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

Так что это не вопрос «против». Для этого вам могут понадобиться оба конструктора (-ов) копирования и интерфейс клона.

(Хотя рекомендуемым общедоступным интерфейсом является интерфейс Clone (), а не конструктор.)

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

Контракт Clone () просто «не изменится при первом изменении». Сколько из графика, который вы должны скопировать, или как избежать бесконечной рекурсии, чтобы это произошло, не должно касаться вызывающего.

Внедрение ICloneable не рекомендуется из-за того, что не указано, является ли это глубокой или мелкой копией, поэтому я бы пошел на конструктор или сам что-то реализовал. Возможно, назовите его DeepCopy (), чтобы сделать его действительно очевидным!

У вас возникнут проблемы с конструкторами копирования и абстрактными classами. Представьте, что вы хотите сделать следующее:

 abstract class A { public A() { } public A(A ToCopy) { X = ToCopy.X; } public int X; } class B : A { public B() { } public B(B ToCopy) : base(ToCopy) { Y = ToCopy.Y; } public int Y; } class C : A { public C() { } public C(C ToCopy) : base(ToCopy) { Z = ToCopy.Z; } public int Z; } class Program { static void Main(string[] args) { List list = new List(); B b = new B(); bX = 1; bY = 2; list.Add(b); C c = new C(); cX = 3; cZ = 4; list.Add(c); List cloneList = new List(); //Won't work //foreach (A a in list) // cloneList.Add(new A(a)); //Not this time batman! //Works, but is nasty for anything less contrived than this example. foreach (A a in list) { if(a is B) cloneList.Add(new B((B)a)); if (a is C) cloneList.Add(new C((C)a)); } } } 

Сразу после выполнения вышеизложенного вы начинаете желать, чтобы вы использовали интерфейс или решили для реализации DeepCopy () / ICloneable.Clone ().

Проблема с ICloneable – это как намерение, так и последовательность. Никогда не бывает ясно, является ли это глубокой или мелкой копией. Из-за этого он, вероятно, никогда не используется только так или иначе.

Я не считаю, что конструктор публичных копий может быть более ясным в этом вопросе.

Тем не менее, я бы представил систему методов, которая работает для вас и реле намерения (a’la несколько самостоятельной документации)

Если объект, который вы пытаетесь скопировать, является Serializable, вы можете клонировать его, сериализируя его и десериализируя. Тогда вам не нужно писать конструктор копирования для каждого classа.

У меня нет доступа к коду прямо сейчас, но это что-то вроде этого

 public object DeepCopy(object source) { // Copy with Binary Serialization if the object supports it // If not try copying with XML Serialization // If not try copying with Data contract Serailizer, etc } 

Это зависит от семантики копии рассматриваемого classа, которую вы должны определить как разработчика. Выбранный метод обычно основан на предполагаемых вариантах использования classа. Может быть, будет смысл реализовать оба метода. Но оба имеют сходный недостаток – не совсем ясно, какой метод копирования они реализуют. Это должно быть четко указано в документации для вашего classа.

Для меня есть:

 // myobj is some transparent proxy object var state = new ObjectState(myobj.State); // do something myobject = GetInstance(); var newState = new ObjectState(myobject.State); if (!newState.Equals(state)) throw new Exception(); 

вместо:

 // myobj is some transparent proxy object var state = myobj.State.Clone(); // do something myobject = GetInstance(); var newState = myobject.State.Clone(); if (!newState.Equals(state)) throw new Exception(); 

выглядел как более ясное заявление о намерениях.

Я думаю, что должен быть стандартный шаблон для клонируемых объектов, хотя я не уверен, какой именно шаблон должен быть. Что касается клонирования, то, по-видимому, существуют три типа classов:

  1. Те, которые явно поддерживают глубокое клонирование
  2. Те, где клонирование по порядку будет работать как глубокое клонирование, но которые не имеют и не нуждаются в явной поддержке.
  3. Те, которые не могут быть полезны для глубокого клонирования, и когда клонирование по порядку будет приводить к плохим результатам.

Насколько я могу судить, единственный способ (по крайней мере, в .net 2.0) получить новый объект того же classа, что и существующий объект, – использовать MemberwiseClone. Хороший шаблон, похоже, должен иметь функцию «новый» / «Тени» Clone, которая всегда возвращает текущий тип, чье определение всегда вызывает MemberwiseClone, а затем вызывает защищенную виртуальную подпрограмму CleanupClone (originalObject). Процедура CleanupCode должна вызвать base.Cleanupcode для обработки запросов клонирования базового типа, а затем добавить свою собственную очистку. Если в процедуре клонирования должен использоваться исходный объект, это должно быть typecast, но в противном случае единственным типом будет только вызов MemberwiseClone.

К сожалению, самый низкий уровень classа, который имел тип (1) выше, а не тип (2), должен был быть закодирован, чтобы предположить, что его более низкие типы не нуждаются в какой-либо явной поддержке клонирования. Я действительно не вижу никакого способа обойти это.

Тем не менее, я думаю, что определенный шаблон будет лучше, чем ничего.

Кстати, если известно, что базовый тип поддерживает iCloneable, но не знает имя используемой функции, есть ли способ ссылаться на функцию iCloneable.Clone базового типа?

Если вы прочитаете все интересные ответы и обсуждения, вы все равно можете спросить себя, как именно вы копируете свойства – все они явно, или есть более элегантный способ сделать это? Если это ваш оставшийся вопрос, взгляните на это (в StackOverflow):

Как я могу «глубоко» клонировать свойства сторонних classов с помощью универсального метода расширения?

В нем описывается, как реализовать метод расширения CreateCopy() который создает «глубокую» копию объекта, включая все свойства (без необходимости вручную копировать свойство по свойству).

  • Неопределенное поведение и точки последовательности перезагружены
  • Когда используется ключевое слово C # ref всегда хорошая идея?
  • Преобразование данных в JSON в C #
  • В чем преимущества маркировки поля как `readonly` в C #?
  • Исключение Active Directory COM - произошла ошибка операций (0x80072020)
  • Если мой интерфейс должен вернуть Task, что является лучшим способом для реализации без операции?
  • Может ли код, который действителен как в C, так и в C ++, создает другое поведение при компиляции на каждом языке?
  • Как добавить двойные кавычки к строке, находящейся внутри переменной?
  • Можете ли вы скомпилировать C #, чтобы она не нуждалась в .NET Framework во время выполнения?
  • POSTing JSON для URL через WebClient в C #
  • Общее представление DbDataReader для отображения
  • Давайте будем гением компьютера.