Есть ли конкретная причина, по которой дженерик ICloneable<T>
не существует?
Было бы намного удобнее, если бы мне не нужно было разыгрывать его каждый раз, когда я что-то клонирую.
Есть ли конкретная причина, по которой дженерик ICloneable<T>
не существует?
Было бы намного удобнее, если бы мне не нужно было разыгрывать его каждый раз, когда я что-то клонирую.
Ответы:
Сейчас ICloneable считается плохим API, поскольку он не определяет, является ли результат глубокой или мелкой копией. Я думаю, что именно поэтому они не улучшают этот интерфейс.
Возможно, вы можете использовать типизированный метод расширения клонирования, но я думаю, что для этого потребуется другое имя, поскольку методы расширения имеют меньший приоритет, чем исходные.
List<T>
бы у меня был метод клонирования, я ожидал бы, что он выдаст тот, List<T>
чьи элементы имеют те же идентификаторы, что и в исходном списке, но я ожидаю, что любые внутренние структуры данных будут дублироваться по мере необходимости, чтобы гарантировать, что ничего, что сделано с одним списком, не повлияет в тождестве элементов , хранящиеся в других. Где двусмысленность? Более серьезная проблема с клонированием связана с вариацией «алмазной проблемы»: если она CloneableFoo
наследуется от [не подлежит публичному клонированию] Foo
, то должна CloneableDerivedFoo
identity
в самом списке (в случае списка списков)? Однако, игнорируя это, ваши ожидания - не единственная возможная идея, которую люди могут иметь при вызове или реализации Clone
. Что если авторы библиотек, реализующие какой-то другой список, не будут соответствовать вашим ожиданиям? API должен быть тривиально однозначным, а не однозначно однозначным.
Clone
все его части, он не будет работать предсказуемо - в зависимости от того, была ли эта часть реализована вами или этим человеком кто любит глубокое клонирование Ваша точка зрения о шаблонах верна, но наличие IMHO в API недостаточно ясно - оно либо должно вызываться ShallowCopy
для подчеркивания, либо не указывается вообще.
В дополнение к ответу Андрея (с которым я согласен, +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)
ICloneable<out T>
может быть весьма полезным, если он унаследован от ISelf<out T>
одного метода Self
типа T
. Нередко не нужно «что-то клонируемое», но вполне может понадобиться то, T
что можно клонировать. Если реализуется клонируемый объект ISelf<itsOwnType>
, подпрограмма, для которой требуется клонируемый объект , T
может принимать параметр типа ICloneable<T>
, даже если не все клонируемые производные T
имеют общего предка.
ICloneable<T>
может быть полезно нечто подобное , хотя более полезной может быть более широкая структура для поддержки параллельных изменяемых и неизменяемых классов. Другими словами, код, который должен видеть, что Foo
содержит какой-то тип, но не собирается его мутировать или ожидать, что он никогда не изменится, мог бы использовать IReadableFoo
, в то время как ...
Foo
может использовать ImmutableFoo
код while, который для манипулирования им может использовать a MutableFoo
. Код, заданный любым типом, IReadableFoo
должен иметь возможность получить изменяемую или неизменяемую версию. Такая структура была бы хороша, но, к сожалению, я не могу найти какой-либо хороший способ настроить вещи в общем виде. Если бы существовал непротиворечивый способ сделать обертку только для чтения для класса, такую вещь можно было бы использовать в сочетании с ICloneable<T>
созданием неизменной копии класса, который содержит T
'.
List<T>
, так что клонированный List<T>
является новой коллекцией, содержащей указатели на все те же объекты в исходной коллекции, есть два простых способа сделать это без ICloneable<T>
. Первый - это Enumerable.ToList()
метод расширения List<foo> clone = original.ToList();
. Второй - это List<T>
конструктор, который принимает IEnumerable<T>
: List<foo> clone = new List<foo>(original);
Я подозреваю, что метод расширения, вероятно, просто вызывает конструктор, но оба они будут делать то, что вы запрашиваете. ;)
Я думаю, что вопрос «почему» не нужен. Существует множество интерфейсов / классов / и т. Д., Которые очень полезны, но не являются частью базовой библиотеки .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>
?
использование
Итак, у вас есть класс, который его реализует. Если раньше у вас был метод, который требуется 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>
. Выбрав второй, вы нарушите сам интерфейс, создав методы, которые должны делать то же самое, возвращая разные объекты.
точка
Вы хотите 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;
}
Он называется Copy, чтобы не вступать в противоречие с текущими методами Clone (компилятор предпочитает собственные объявленные методы типа, а не расширения). class
Ограничение существует для скорости (не требует нулевой проверки и т.д.).
Я надеюсь, что это проясняет причину, почему не сделать ICloneable<T>
. Тем не менее, рекомендуется не осуществлять ICloneable
вообще.
ICloneable
значения - для типов значений, где оно может обойти бокс метода Clone, и это подразумевает, что у вас есть значение без коробки. И поскольку структуры могут быть клонированы (поверхностно) автоматически, нет необходимости реализовывать их (если вы не укажете, что это означает глубокое копирование).
Хотя вопрос очень старый (5 лет с момента написания этого ответа :) и уже был дан ответ, но я обнаружил, что эта статья отвечает на вопрос довольно хорошо, проверьте его здесь
РЕДАКТИРОВАТЬ:
Вот цитата из статьи, которая отвечает на вопрос (обязательно прочитайте всю статью, она включает в себя другие интересные вещи):
В Интернете есть много ссылок, указывающих на сообщение в блоге Брэда Абрамса в 2003 году - в то время работавшее в Microsoft - в котором обсуждаются некоторые мысли о ICloneable. Запись в блоге можно найти по этому адресу: Реализация ICloneable . Несмотря на вводящее в заблуждение название, эта запись в блоге призывает не внедрять ICloneable, в основном из-за мелкой / глубокой путаницы. Статья заканчивается прямым предложением: если вам нужен механизм клонирования, определите свою собственную методику клонирования или копирования и убедитесь, что вы четко документируете, является ли она глубокой или мелкой копией. Соответствующий шаблон:
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
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
бы можно было ограничить T
совпадение с его собственным типом, это не заставило бы реализацию Clone()
возвращать что-либо, отдаленно напоминающее объект, на котором оно было клонировано. Кроме того, я хотел бы предположить, что если кто-то использует ковариацию интерфейса, может быть лучше иметь классы, которые реализуют ICloneable
запечатывание, иметь интерфейс, ICloneable<out T>
включающий Self
свойство, которое, как ожидается, должно возвращать себя, и ...
ICloneable<BaseType>
илиICloneable<ICloneable<BaseType>>
. Рассматриваемый BaseType
вопрос должен иметь protected
метод клонирования, который будет вызываться реализуемым типом ICloneable
. Такая конструкция допускает возможность того, что кто-то может пожелать иметь a Container
, a CloneableContainer
, a FancyContainer
и a CloneableFancyContainer
, причем последний может быть использован в коде, который требует клонируемого производного Container
или который требует a FancyContainer
(но не волнует, является ли он клонируемым).
FancyList
тип, который можно разумно клонировать, но производная может автоматически сохранять свое состояние в файле на диске (указанном в конструкторе). Производный тип не может быть клонирован, потому что его состояние будет привязано к состоянию изменяемого синглтона (файла), но это не должно исключать использования производного типа в местах, где требуется большинство функций, FancyList
но не требуется клонировать это.
Это очень хороший вопрос ... Вы можете сделать свой собственный, хотя:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Андрей говорит, что это плохой API, но я ничего не слышал об устаревании этого интерфейса. И это сломало бы тонны интерфейсов ... Метод Clone должен выполнять поверхностное копирование. Если объект также обеспечивает глубокое копирование, можно использовать перегруженный клон (bool deep).
РЕДАКТИРОВАТЬ: Шаблон, который я использую для «клонирования» объекта, передает прототип в конструктор.
class C
{
public C ( C prototype )
{
...
}
}
Это удаляет любые потенциальные избыточные ситуации реализации кода. Кстати, говоря об ограничениях ICloneable, не правда ли, сам объект решает, должен ли быть выполнен неглубокий клон или глубокий клон, или даже частично неглубокий / частично глубокий клон? Должны ли мы действительно заботиться, пока объект работает, как задумано? В некоторых случаях хорошая реализация Clone вполне может включать как поверхностное, так и глубокое клонирование.