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