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

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

200 голосов
/ 11 февраля 2009

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

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

Ответы [ 9 ]

136 голосов
/ 11 февраля 2009

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

104 голосов
/ 11 февраля 2009

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

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

17 голосов
/ 25 февраля 2012

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

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

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

12 голосов
/ 11 февраля 2009

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

Прочитав недавно статью Почему копирование объекта - ужасная вещь? , я думаю, что этот вопрос требует дополнительной прояснения. Другие ответы здесь дают хорошие советы, но все же ответ не полный - почему нет 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. Point

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

    Если первая причина еще не убедила вас, вы можете возразить, что ICloneable<T> также может работать ограничительно, чтобы ограничить тип, возвращаемый методом clone. Однако противный программист может реализовать 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().

Итак, давайте посмотрим на «окончательное» решение - что в 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;
}

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

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

8 голосов
/ 11 февраля 2009

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

public interface ICloneable<T> : ICloneable
        where T : ICloneable<T>
{
    new T Clone();
}
2 голосов
/ 29 октября 2014

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

EDIT:

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

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

1 голос
/ 20 июля 2012

Большая проблема в том, что они не могут ограничить 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
0 голосов
/ 11 февраля 2009

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

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

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

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

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

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

...