Есть ли конкретная причина, по которой дженерик ICloneable<T>
не существует?
Было бы намного удобнее, если бы мне не нужно было разыгрывать его каждый раз, когда я что-то клонирую.
c#
.net
icloneable
Bluenuance
источник
источник
Ответы:
Сейчас ICloneable считается плохим API, поскольку он не определяет, является ли результат глубокой или мелкой копией. Я думаю, что именно поэтому они не улучшают этот интерфейс.
Возможно, вы можете использовать типизированный метод расширения клонирования, но я думаю, что для этого потребуется другое имя, поскольку методы расширения имеют меньший приоритет, чем исходные.
источник
List<T>
бы у меня был метод клонирования, я ожидал бы, что он выдаст тот,List<T>
чьи элементы имеют те же идентификаторы, что и в исходном списке, но я ожидаю, что любые внутренние структуры данных будут дублироваться по мере необходимости, чтобы гарантировать, что ничего, что сделано с одним списком, не повлияет в тождестве элементов , хранящиеся в других. Где двусмысленность? Более серьезная проблема с клонированием связана с вариацией «алмазной проблемы»: если онаCloneableFoo
наследуется от [не подлежит публичному клонированию]Foo
, то должнаCloneableDerivedFoo
identity
в самом списке (в случае списка списков)? Однако, игнорируя это, ваши ожидания - не единственная возможная идея, которую люди могут иметь при вызове или реализацииClone
. Что если авторы библиотек, реализующие какой-то другой список, не будут соответствовать вашим ожиданиям? API должен быть тривиально однозначным, а не однозначно однозначным.Clone
все его части, он не будет работать предсказуемо - в зависимости от того, была ли эта часть реализована вами или этим человеком кто любит глубокое клонирование Ваша точка зрения о шаблонах верна, но наличие IMHO в API недостаточно ясно - оно либо должно вызыватьсяShallowCopy
для подчеркивания, либо не указывается вообще.В дополнение к ответу Андрея (с которым я согласен, +1) - когда это
ICloneable
будет сделано, вы также можете выбрать явную реализацию, чтобы общественностьClone()
возвращала типизированный объект:Конечно, есть вторая проблема с общим
ICloneable<T>
- наследование.Если бы у меня был:
И я реализовал
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, который для манипулирования им может использовать aMutableFoo
. Код, заданный любым типом,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.
Но, в основном, вы можете сделать это самостоятельно.
источник
Довольно просто написать интерфейс, если он вам нужен:
источник
Прочитав недавно статью « Почему копирование объекта - ужасная вещь? Думаю, этот вопрос требует дополнительного уточнения. Другие ответы здесь дают хорошие советы, но все же ответ не полный - почему нет
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 virtual object Clone()
.Итак, давайте посмотрим «окончательное» решение - что в C # действительно намеревается дать нам комфорт?
Методы расширения!
Он называется Copy, чтобы не вступать в противоречие с текущими методами Clone (компилятор предпочитает собственные объявленные методы типа, а не расширения).
class
Ограничение существует для скорости (не требует нулевой проверки и т.д.).Я надеюсь, что это проясняет причину, почему не сделать
ICloneable<T>
. Тем не менее, рекомендуется не осуществлятьICloneable
вообще.источник
ICloneable
значения - для типов значений, где оно может обойти бокс метода Clone, и это подразумевает, что у вас есть значение без коробки. И поскольку структуры могут быть клонированы (поверхностно) автоматически, нет необходимости реализовывать их (если вы не укажете, что это означает глубокое копирование).Хотя вопрос очень старый (5 лет с момента написания этого ответа :) и уже был дан ответ, но я обнаружил, что эта статья отвечает на вопрос довольно хорошо, проверьте его здесь
РЕДАКТИРОВАТЬ:
Вот цитата из статьи, которая отвечает на вопрос (обязательно прочитайте всю статью, она включает в себя другие интересные вещи):
источник
Большая проблема заключается в том, что они не могут ограничить T тем же классом. Например, что бы помешало вам сделать это:
Им нужно ограничение параметров, например:
источник
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
. Такая конструкция допускает возможность того, что кто-то может пожелать иметь aContainer
, aCloneableContainer
, aFancyContainer
и aCloneableFancyContainer
, причем последний может быть использован в коде, который требует клонируемого производногоContainer
или который требует aFancyContainer
(но не волнует, является ли он клонируемым).FancyList
тип, который можно разумно клонировать, но производная может автоматически сохранять свое состояние в файле на диске (указанном в конструкторе). Производный тип не может быть клонирован, потому что его состояние будет привязано к состоянию изменяемого синглтона (файла), но это не должно исключать использования производного типа в местах, где требуется большинство функций,FancyList
но не требуется клонировать это.Это очень хороший вопрос ... Вы можете сделать свой собственный, хотя:
Андрей говорит, что это плохой API, но я ничего не слышал об устаревании этого интерфейса. И это сломало бы тонны интерфейсов ... Метод Clone должен выполнять поверхностное копирование. Если объект также обеспечивает глубокое копирование, можно использовать перегруженный клон (bool deep).
РЕДАКТИРОВАТЬ: Шаблон, который я использую для «клонирования» объекта, передает прототип в конструктор.
Это удаляет любые потенциальные избыточные ситуации реализации кода. Кстати, говоря об ограничениях ICloneable, не правда ли, сам объект решает, должен ли быть выполнен неглубокий клон или глубокий клон, или даже частично неглубокий / частично глубокий клон? Должны ли мы действительно заботиться, пока объект работает, как задумано? В некоторых случаях хорошая реализация Clone вполне может включать как поверхностное, так и глубокое клонирование.
источник