Вопрос

Есть ли особая причина, по которой общий ICloneable<T> не существует?

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

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

Решение

ICloneable сейчас считается плохим API, поскольку он не определяет, является ли результат глубокой или поверхностной копией.Думаю, именно поэтому они не улучшают этот интерфейс.

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

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

В дополнение к ответу Андрея (с которым согласен, +1) - когда ICloneable является готово, вы также можете выбрать явную реализацию, чтобы сделать ее общедоступной. Clone() вернуть типизированный объект:

public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}

Конечно, есть вторая проблема с универсальным ICloneable<T> - наследование.

Если бы у меня был:

public class Foo {}
public class Bar : Foo {}

И я реализовал ICloneable<T>, тогда я реализую ICloneable<Foo>? ICloneable<Bar>?Вы быстро начинаете реализовывать множество одинаковых интерфейсов...Сравните с актерским составом...и неужели это так плохо?

Мне нужно спросить, что именно вы бы сделали с интерфейсом Кроме как реализовать это?Интерфейсы обычно полезны только тогда, когда вы к ним приводите (т. е. поддерживает ли этот класс «IBar») или имеют параметры или сеттеры, которые его принимают (т. е. я беру «IBar»).С ICloneable мы просмотрели всю структуру и не смогли найти ни одного использования, которое было бы чем-то иным, чем ее реализация.Нам также не удалось найти какое-либо использование в «реальном мире», которое также делало бы что-то кроме реализации (в примерно 60 000 приложениях, к которым у нас есть доступ).

Теперь, если вы просто хотите применить шаблон, который вы хотите реализовать в своих «клонируемых» объектах, это вполне нормальное использование — и вперед.Вы также можете решить, что именно для вас означает «клонирование» (т.е. глубокое или поверхностное).Однако в этом случае нам (BCL) нет необходимости определять это.Мы определяем абстракции в BCL только тогда, когда необходимо обмениваться экземплярами, типизированными как эта абстракция, между несвязанными библиотеками.

Дэвид Кин (команда BCL)

Думаю, вопрос «почему» излишен.Существует много интерфейсов/классов/и т.д.что очень полезно, но не является частью базовой библиотеки .NET Frameworku.

Но, в основном, вы можете сделать это самостоятельно.

public interface ICloneable<T> : ICloneable {
    new T Clone();
}

public abstract class CloneableBase<T> : ICloneable<T> where T : CloneableBase<T> {
    public abstract T Clone();
    object ICloneable.Clone() { return this.Clone(); }
}

public abstract class CloneableExBase<T> : CloneableBase<T> where T : CloneableExBase<T> {
    protected abstract T CreateClone();
    protected abstract void FillClone( T clone );
    public override T Clone() {
        T clone = this.CreateClone();
        if ( object.ReferenceEquals( clone, null ) ) { throw new NullReferenceException( "Clone was not created." ); }
        return clone
    }
}

public abstract class PersonBase<T> : CloneableExBase<T> where T : PersonBase<T> {
    public string Name { get; set; }

    protected override void FillClone( T clone ) {
        clone.Name = this.Name;
    }
}

public sealed class Person : PersonBase<Person> {
    protected override Person CreateClone() { return new Person(); }
}

public abstract class EmployeeBase<T> : PersonBase<T> where T : EmployeeBase<T> {
    public string Department { get; set; }

    protected override void FillClone( T clone ) {
        base.FillClone( clone );
        clone.Department = this.Department;
    }
}

public sealed class Employee : EmployeeBase<Employee> {
    protected override Employee CreateClone() { return new Employee(); }
}

Это довольно легко напиши интерфейс сам если вам это нужно:

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}

Прочитав недавно статью Почему копирование объекта — это ужасно?, Думаю, этот вопрос нуждается в дополнительном разъяснении.Другие ответы здесь дают хорошие советы, но ответ все же неполный - почему бы и нет. ICloneable<T>?

  1. Применение

    Итак, у вас есть класс, который это реализует.Раньше у вас был метод, который хотел ICloneable, теперь он должен быть универсальным, чтобы принять ICloneable<T>.Вам нужно будет отредактировать его.

    Тогда у вас мог бы быть метод, который проверяет, является ли объект is ICloneable.Что теперь?Ты не можешь сделать is ICloneable<> и поскольку вы не знаете тип объекта при компиляции, вы не можете сделать метод универсальным.Первая настоящая проблема.

    Поэтому вам нужно иметь оба ICloneable<T> и ICloneable, первый реализует последний.Таким образом, разработчику необходимо будет реализовать оба метода: object Clone() и T Clone().Нет, спасибо, мы уже достаточно повеселились IEnumerable.

    Как уже отмечалось, существует также сложность наследования.Хотя может показаться, что ковариация решает эту проблему, производный тип должен реализовать ICloneable<T> своего типа, но уже существует метод с той же сигнатурой (= параметры, в основном) - метод Clone() базового класса.Делать явным интерфейс нового метода клонирования бессмысленно, вы потеряете преимущество, к которому стремились при создании. ICloneable<T>.Так что добавьте new ключевое слово.Но не забывайте, что вам также потребуется переопределить базовый класс. Clone() (реализация должна оставаться единой для всех производных классов, т.е.чтобы вернуть один и тот же объект из каждого метода клонирования, поэтому базовый метод клонирования должен быть virtual)!Но, к сожалению, вы не можете и то, и другое. override и new методы с той же сигнатурой.Выбрав первое ключевое слово, вы потеряете цель, которую хотели достичь при добавлении ICloneable<T>.Выбрав второй вариант, вы сломаете сам интерфейс, заставив методы, которые должны делать одно и то же, возвращать разные объекты.

  2. Точка

    Вы хотите ICloneable<T> для комфорта, но комфорт — это не то, для чего предназначены интерфейсы, их смысл (в общем ООП) — унифицировать поведение объектов (хотя в C# он ограничивается унификацией внешнего поведения, напримерметоды и свойства, а не их работа).

    Если первая причина вас еще не убедила, вы можете возразить, что ICloneable<T> также может работать ограничительно, чтобы ограничить тип, возвращаемый методом клонирования.Однако противный программист может реализовать ICloneable<T> где T не является типом, который его реализует.Итак, чтобы добиться ограничения, вы можете добавить хорошее ограничение к общему параметру:
    public interface ICloneable<T> : ICloneable where T : ICloneable<T>
    Конечно, более ограничительный, чем тот, у которого нет where, вы все равно не можете это ограничить Т — это тип, реализующий интерфейс (вы можете получить его из ICloneable<T> другого типа, который его реализует).

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

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

Но вернемся к вопросу: на самом деле вам нужно удобство при клонировании объекта.Есть два способа сделать это:

Дополнительные методы

public class Base : ICloneable
{
    public Base Clone()
    {
        return this.CloneImpl() as Base;
    }

    object ICloneable.Clone()
    {
        return this.CloneImpl();
    }

    protected virtual object CloneImpl()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public new Derived Clone()
    {
        return this.CloneImpl() as Derived;
    }

    protected override object CloneImpl()
    {
        return new Derived();
    }
}

Это решение обеспечивает пользователям комфорт и ожидаемое поведение, но его реализация слишком длительна.Если бы мы не хотели, чтобы «удобный» метод возвращал текущий тип, гораздо проще просто использовать public virtual object Clone().

Итак, давайте посмотрим на «окончательное» решение: что в C# действительно предназначено для того, чтобы нас утешить?

Методы расширения!

public class Base : ICloneable
{
    public virtual object Clone()
    {
        return new Base();
    }
}

public class Derived : Base
{
    public override object Clone()
    {
        return new Derived();
    }
}

public static T Copy<T>(this T obj) where T : class, ICloneable
{
    return obj.Clone() as T;
}

Он назван Копировать не сталкиваться с течением Клонировать методы (компилятор предпочитает собственные объявленные методы типа методам расширения).В class существует ограничение на скорость (не требует проверки на нулевое значение и т. д.).

Я надеюсь, что это проясняет причину, почему не сделать ICloneable<T>.Однако не рекомендуется выполнять ICloneable совсем.

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

РЕДАКТИРОВАТЬ:

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

Есть много ссылок в Интернете, указывающих на сообщение в блоге 2003 года Брэдом Абрамсом - в то время, когда он работал в Microsoft - в котором некоторые мысли о ICloneable обсуждаются.Запись блога можно найти по этому адресу: Реализация ICloneable.Несмотря на обманчивость название, эта запись в блоге призывает не реализовывать ICloneable, в основном из-за мелкой/глубокой путаницы.Статья заканчивается на прямую предложение:Если вам нужен механизм клонирования, определите свой собственный Clone, или Скопируйте методологию и убедитесь, что вы четко документируете, является ли это глубокая или мелкая копия.Подходящий шаблон: public <type> Copy();

Большая проблема заключается в том, что они не могли ограничить T одним и тем же классом.Например, что помешает вам это сделать:

interface IClonable<T>
{
    T Clone();
}

class Dog : IClonable<JackRabbit>
{
    //not what you would expect, but possible
    JackRabbit Clone()
    {
        return new JackRabbit();
    }

}

Им нужно ограничение параметров, например:

interfact IClonable<T> where T : implementing_type

Это очень хороший вопрос...Однако вы можете сделать свой собственный:

interface ICloneable<T> : ICloneable
{
  new T Clone ( );
}

Андрей говорит, что это плохой API, но я ничего не слышал о том, что этот интерфейс устарел.И это сломало бы тонны интерфейсов...Метод Clone должен выполнять поверхностное копирование.Если объект также предоставляет глубокую копию, можно использовать перегруженный клон ( bool deep ).

РЕДАКТИРОВАТЬ:Шаблон, который я использую для «клонирования» объекта, передает прототип в конструктор.

class C
{
  public C ( C prototype )
  {
    ...
  }
}

Это устраняет любые потенциальные ситуации реализации избыточного кода.Кстати, говоря об ограничениях ICloneable, разве сам объект не должен решать, следует ли выполнять поверхностное клонирование, глубокое клонирование или даже частично поверхностное/частично глубокое клонирование?Стоит ли нам волноваться, пока объект работает так, как задумано?В некоторых случаях хорошая реализация клонирования вполне может включать как поверхностное, так и глубокое клонирование.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top