Почему нет ICloneable <T>?

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

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

Ответ 1

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

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

Ответ 2

В дополнение к ответу Andrey (с которым я согласен, +1) - когда ICloneable выполняется, вы также можете выбрать явную реализацию, чтобы сделать public 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>? Вы быстро начинаете внедрять множество идентичных интерфейсов... Сравните с актером... и действительно ли так плохо?

Ответ 3

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

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

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

Ответ 4

Я думаю, что вопрос "почему" бесполезен. Существует много интерфейсов/классов/etc... что очень полезно, но не является частью базовой библиотеки .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(); }
}

Ответ 5

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

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

Ответ 6

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

  • Использование

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

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

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

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

  • точка

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

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

Итак, посмотрим на "окончательное" решение - что на С# действительно намерено дать нам комфорт?

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

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;
}

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

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

Ответ 7

Большая проблема заключается в том, что они не могут ограничивать 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

Ответ 8

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

EDIT:

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

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

Ответ 9

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

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

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

EDIT: шаблон, который я использую для "клонирования" объекта, передает прототип в конструкторе.

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

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