Скопируйте конструктора по сравнению с Клоном ()

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

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

107
задан Community 23 May 2017 в 10:31
поделиться

7 ответов

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

12
ответ дан 24 November 2019 в 03:41
поделиться

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

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

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

4
ответ дан 24 November 2019 в 03:41
поделиться

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

Для меня наличие:

// 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();

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

2
ответ дан 24 November 2019 в 03:41
поделиться

Вы не должны выводить из ICloneable.

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

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

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

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

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

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

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

Это обсуждается в .net Framework Design Guidelines и в блоге Брэда Абрамса

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

84
ответ дан 24 November 2019 в 03:41
поделиться

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

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

Констуктор копирования также страдает от одной из проблем 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();
    }
}
31
ответ дан 24 November 2019 в 03:41
поделиться

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

У меня сейчас нет доступа к коду, но он выглядит примерно так

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
}
3
ответ дан 24 November 2019 в 03:41
поделиться

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

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

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

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

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

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

0
ответ дан 24 November 2019 в 03:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: