Gibt es einen bestimmten Grund, warum es kein Generikum ICloneable<T>
gibt?
Es wäre viel bequemer, wenn ich es nicht jedes Mal wirken müsste, wenn ich etwas klone.
c#
.net
icloneable
Bluenuance
quelle
quelle
Antworten:
ICloneable wird jetzt als schlechte API angesehen, da nicht angegeben wird, ob das Ergebnis eine tiefe oder eine flache Kopie ist. Ich denke, deshalb verbessern sie diese Schnittstelle nicht.
Sie können wahrscheinlich eine typisierte Klon-Erweiterungsmethode ausführen, aber ich denke, sie würde einen anderen Namen erfordern, da Erweiterungsmethoden weniger Priorität haben als die ursprünglichen.
quelle
List<T>
es eine Klonmethode gäbe, würde ich erwarten, dass sie eine ergibt,List<T>
deren Elemente die gleichen Identitäten wie die in der ursprünglichen Liste haben, aber ich würde erwarten, dass alle internen Datenstrukturen nach Bedarf dupliziert werden, um sicherzustellen, dass nichts, was mit einer Liste gemacht wird, Auswirkungen hat die Identitäten der im anderen gespeicherten Elemente. Wo ist die Mehrdeutigkeit? Ein größeres Problem beim Klonen ist eine Variation des "Diamantproblems": Wenn esCloneableFoo
von [nicht öffentlich klonbar] geerbt wirdFoo
, sollte esCloneableDerivedFoo
von ...identity
Liste selbst betrachten (im Fall einer Liste von Listen)? Wenn Sie dies jedoch ignorieren, ist Ihre Erwartung nicht die einzig mögliche Idee, die Menschen beim Aufrufen oder Implementieren haben könnenClone
. Was ist, wenn Bibliotheksautoren, die eine andere Liste implementieren, nicht Ihren Erwartungen entsprechen? Die API sollte trivial eindeutig und nicht eindeutig sein.Clone
alle ihre Teile aufruft, funktioniert dies nicht vorhersehbar - je nachdem, ob dieser Teil von Ihnen oder dieser Person implementiert wurde wer mag tiefes Klonen. Ihr Standpunkt zu den Mustern ist gültig, aber IMHO in der API zu haben, ist nicht klar genug - es sollte entweder aufgerufen werdenShallowCopy
, um den Punkt zu betonen, oder überhaupt nicht bereitgestellt werden.Neben Andrey Antwort (die ich mit einigen, +1) - wenn
ICloneable
wird getan, können Sie auch explizite Implementierung wählen , die öffentlich zu machenClone()
Rückkehr ein typisierte Objekt:Natürlich gibt es ein zweites Problem mit einer generischen
ICloneable<T>
Vererbung.Wenn ich habe:
Und ich habe implementiert
ICloneable<T>
, dann implementiere ichICloneable<Foo>
?ICloneable<Bar>
? Sie beginnen schnell mit der Implementierung vieler identischer Schnittstellen ... Vergleichen Sie mit einer Besetzung ... und ist es wirklich so schlimm?quelle
Ich muss fragen, was genau würden Sie mit der Schnittstelle tun, außer sie zu implementieren? Schnittstellen sind normalerweise nur dann nützlich, wenn Sie sie umwandeln (dh diese Klasse unterstützt 'IBar') oder Parameter oder Setter haben, die sie übernehmen (dh ich nehme eine 'IBar'). Mit ICloneable haben wir das gesamte Framework durchgearbeitet und keine einzige Verwendung gefunden, die etwas anderes als eine Implementierung war. Wir haben auch keine Verwendung in der "realen Welt" gefunden, die etwas anderes tut als sie zu implementieren (in den ~ 60.000 Apps, auf die wir Zugriff haben).
Wenn Sie nur ein Muster erzwingen möchten, das Ihre "klonbaren" Objekte implementieren sollen, ist dies eine völlig gute Verwendung - und fahren Sie fort. Sie können auch genau entscheiden, was "Klonen" für Sie bedeutet (dh tief oder flach). In diesem Fall müssen wir (die BCL) sie jedoch nicht definieren. Wir definieren Abstraktionen in der BCL nur, wenn Instanzen, die als diese Abstraktion eingegeben wurden, zwischen nicht verwandten Bibliotheken ausgetauscht werden müssen.
David Kean (BCL-Team)
quelle
ICloneable<out T>
könnte sehr nützlich sein , wenn es von geerbtISelf<out T>
, mit einer einzigen MethodeSelf
des TypsT
. Man braucht nicht oft "etwas, das klonbar ist", aber man braucht sehr gut etwasT
, das klonbar ist. Wenn ein klonbares Objekt implementiert wirdISelf<itsOwnType>
, kann eine Routine, die ein klonbares Objekt benötigtT
, einen Parameter vom Typ akzeptierenICloneable<T>
, auch wenn nicht alle klonbaren Ableitungen vonT
gemeinsam einen Vorfahren haben.ICloneable<T>
könnte dafür nützlich sein, obwohl ein breiterer Rahmen für die Aufrechterhaltung paralleler veränderlicher und unveränderlicher Klassen hilfreicher sein könnte. Mit anderen Worten, Code, der sehen muss, was eine Art vonFoo
enthält, aber weder mutiert noch erwartet, dass er sich nie ändern wird, könnte eineIReadableFoo
, während ...Foo
könnte einenImmutableFoo
while-Code verwenden, der manipuliert werden soll, könnte einen verwendenMutableFoo
. Code für jede Art vonIReadableFoo
sollte in der Lage sein, entweder eine veränderbare oder eine unveränderliche Version zu erhalten. Ein solches Framework wäre schön, aber leider kann ich keinen guten Weg finden, um die Dinge generisch einzurichten. Wenn es eine konsistente Möglichkeit gäbe, einen schreibgeschützten Wrapper für eine Klasse zu erstellen, könnte so etwas in Kombination mitICloneable<T>
einer unveränderlichen Kopie einer Klasse verwendet werden, dieT
'enthält.List<T>
, sodass das Klonen eineList<T>
neue Sammlung ist, die Zeiger auf alle gleichen Objekte in der ursprünglichen Sammlung enthält, gibt es zwei einfache Möglichkeiten, dies ohne zu tunICloneable<T>
. Die erste ist dieEnumerable.ToList()
Erweiterungsmethode:List<foo> clone = original.ToList();
Die zweite ist derList<T>
Konstruktor, der Folgendes verwendetIEnumerable<T>
:List<foo> clone = new List<foo>(original);
Ich vermute, dass die Erweiterungsmethode wahrscheinlich nur den Konstruktor aufruft, aber beide tun, was Sie anfordern. ;)Ich denke, die Frage "warum" ist unnötig. Es gibt viele Schnittstellen / Klassen / etc ..., was sehr nützlich ist, aber nicht Teil der .NET Frameworku-Basisbibliothek ist.
Aber hauptsächlich können Sie es selbst tun.
quelle
Es ist ziemlich einfach , die Benutzeroberfläche selbst zu schreiben, wenn Sie sie benötigen:
quelle
Nachdem Sie kürzlich den Artikel gelesen haben, warum das Kopieren eines Objekts eine schreckliche Sache ist? Ich denke, diese Frage erfordert eine zusätzliche Reinigung. Andere Antworten hier liefern gute Ratschläge, aber die Antwort ist immer noch nicht vollständig - warum nein
ICloneable<T>
?Verwendung
Sie haben also eine Klasse, die sie implementiert. Während Sie zuvor eine gewünschte Methode hatten,
ICloneable
muss diese jetzt generisch sein, um akzeptiert zu werdenICloneable<T>
. Sie müssten es bearbeiten.Dann könnten Sie eine Methode haben, die prüft, ob ein Objekt
is ICloneable
. Was jetzt? Sie können dies nicht tun,is ICloneable<>
und da Sie den Typ des Objekts beim Kompilieren nicht kennen, können Sie die Methode nicht generisch machen. Erstes echtes Problem.Sie müssen also beides haben
ICloneable<T>
undICloneable
das erstere muss das letztere implementieren. Ein Implementierer müsste also beide Methoden implementieren -object Clone()
undT Clone()
. Nein, danke, wir haben schon genug Spaß damitIEnumerable
.Wie bereits erwähnt, gibt es auch die Komplexität der Vererbung. Während Kovarianz dieses Problem zu lösen scheint, muss ein abgeleiteter Typ
ICloneable<T>
einen eigenen Typ implementieren , aber es gibt bereits eine Methode mit derselben Signatur (= Parameter im Grunde) - dieClone()
der Basisklasse. Es ist sinnlos, Ihre neue Klonmethodenschnittstelle explizit zu machen. Sie verlieren den Vorteil, den Sie beim Erstellen gesucht habenICloneable<T>
. Fügen Sie also dasnew
Schlüsselwort hinzu. Vergessen Sie jedoch nicht, dass Sie auch die Basisklasse überschreiben müssen 'Clone()
(die Implementierung muss für alle abgeleiteten Klassen einheitlich bleiben, dh um von jeder Klonmethode dasselbe Objekt zurückzugeben, also muss die Basisklonmethode seinvirtual
)! Aber leider kann man nicht beidesoverride
undnew
Methoden mit der gleichen Signatur. Wenn Sie das erste Keyword auswählen, verlieren Sie das Ziel, das Sie beim Hinzufügen haben möchtenICloneable<T>
. Wenn Sie die zweite wählen, brechen Sie die Schnittstelle selbst und erstellen Methoden, die dasselbe tun sollen, und geben unterschiedliche Objekte zurück.Punkt
Sie möchten
ICloneable<T>
Komfort, aber Komfort ist nicht das, wofür Schnittstellen ausgelegt sind. Ihre Bedeutung ist (im Allgemeinen OOP), das Verhalten von Objekten zu vereinheitlichen (obwohl es in C # auf die Vereinheitlichung des äußeren Verhaltens beschränkt ist, z. B. der Methoden und Eigenschaften, nicht ihre Arbeitsweise).Wenn der erste Grund Sie noch nicht überzeugt hat, können Sie Einwände erheben,
ICloneable<T>
die auch restriktiv funktionieren könnten, um den von der Klonmethode zurückgegebenen Typ einzuschränken. Ein böser Programmierer kann jedoch implementieren,ICloneable<T>
wenn T nicht der Typ ist, der es implementiert. Um Ihre Einschränkung zu erreichen, können Sie dem generischen Parameter eine nette Einschränkung hinzufügen:public interface ICloneable<T> : ICloneable where T : ICloneable<T>
Sicherlich restriktiver als die ohne
where
, Sie können immer noch nicht einschränken, dass T der Typ ist, der die Schnittstelle implementiert (Sie könnenICloneable<T>
von einem anderen Typ ableiten das setzt es um).Sie sehen, auch dieser Zweck konnte nicht erreicht werden (das Original
ICloneable
schlägt ebenfalls fehl, keine Schnittstelle kann das Verhalten der implementierenden Klasse wirklich einschränken).Wie Sie sehen können, ist dies ein Beweis dafür, dass die generische Schnittstelle sowohl schwer vollständig zu implementieren als auch wirklich unnötig und nutzlos ist.
Aber zurück zur Frage: Was Sie wirklich suchen, ist Trost beim Klonen eines Objekts. Es gibt zwei Möglichkeiten, dies zu tun:
Zusätzliche Methoden
Diese Lösung bietet Benutzern sowohl Komfort als auch beabsichtigtes Verhalten, ist jedoch auch zu lang für die Implementierung. Wenn wir nicht wollten, dass die "komfortable" Methode den aktuellen Typ zurückgibt, ist es viel einfacher, nur zu haben
public virtual object Clone()
.Schauen wir uns also die "ultimative" Lösung an - was in C # soll uns wirklich Trost geben?
Erweiterungsmethoden!
Es heißt Copy, um nicht mit den aktuellen Clone- Methoden zu kollidieren (der Compiler bevorzugt die deklarierten Methoden des Typs gegenüber den Erweiterungsmethoden). Die
class
Einschränkung gilt für die Geschwindigkeit (erfordert keine Nullprüfung usw.).Ich hoffe das klärt den Grund warum nicht zu machen
ICloneable<T>
. Es wird jedoch empfohlen, überhaupt nicht zu implementierenICloneable
.quelle
ICloneable
ist für Werttypen, bei denen das Boxen der Clone-Methode umgangen werden kann, und dies impliziert, dass Sie den Wert nicht boxen. Und da Strukturen automatisch (flach) geklont werden können, ist es nicht erforderlich, sie zu implementieren (es sei denn, Sie legen fest, dass dies eine tiefe Kopie bedeutet).Obwohl die Frage sehr alt ist (5 Jahre nach dem Schreiben dieser Antworten :) und bereits beantwortet wurde, aber ich fand, dass dieser Artikel die Frage ziemlich gut beantwortet, überprüfen Sie es hier
BEARBEITEN:
Hier ist das Zitat aus dem Artikel, der die Frage beantwortet (lesen Sie unbedingt den vollständigen Artikel, er enthält andere interessante Dinge):
quelle
Ein großes Problem ist, dass sie T nicht auf dieselbe Klasse beschränken konnten. Zum Beispiel, was Sie daran hindern würde:
Sie benötigen eine Parametereinschränkung wie:
quelle
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
sich darauf beschränken könnteT
, seinem eigenen Typ zu entsprechen, würde dies eine Implementierung von nicht zwingenClone()
, etwas zurückzugeben, das dem Objekt, auf das es geklont wurde, aus der Ferne ähnelt. Außerdem würde ich vorschlagen, dass es bei Verwendung der Schnittstellenkovarianz am besten ist, KlassenICloneable
zu versiegeln, die implementiert werden, die SchnittstelleICloneable<out T>
eineSelf
Eigenschaft enthält, von der erwartet wird, dass sie sich selbstICloneable<BaseType>
oderICloneable<ICloneable<BaseType>>
. DieBaseType
fragliche sollte eineprotected
Methode zum Klonen haben, die von dem Typ aufgerufen wird, der implementiertICloneable
. Dieses Design würde die Möglichkeit ermöglichen, dass man aContainer
, aCloneableContainer
, aFancyContainer
und a haben möchteCloneableFancyContainer
, wobei letzteres in Code verwendbar ist, der eine klonbare Ableitung vonContainer
oder erfordert aFancyContainer
(aber es ist egal, ob es klonbar ist).FancyList
Typ haben, der sinnvoll geklont werden könnte, aber ein Derivat könnte seinen Status automatisch in einer Festplattendatei (im Konstruktor angegeben) beibehalten. Der abgeleitete Typ konnte nicht geklont werden, da sein Status an den eines veränderlichen Singletons (der Datei) angehängt wäre. Dies sollte jedoch die Verwendung des abgeleiteten Typs an Orten nicht ausschließen, die die meisten Funktionen einesFancyList
benötigen, aber nicht benötigen um es zu klonen.Es ist eine sehr gute Frage ... Sie könnten jedoch Ihre eigene machen:
Andrey sagt, es sei eine schlechte API, aber ich habe nichts davon gehört, dass diese Schnittstelle veraltet ist. Und das würde Tonnen von Schnittstellen beschädigen ... Die Clone-Methode sollte eine flache Kopie ausführen. Wenn das Objekt auch eine tiefe Kopie bereitstellt, kann ein überladener Klon (bool deep) verwendet werden.
BEARBEITEN: Das Muster, das ich zum "Klonen" eines Objekts verwende, übergibt einen Prototyp im Konstruktor.
Dadurch werden potenzielle Situationen für die Implementierung redundanten Codes beseitigt. Übrigens, wenn man über die Einschränkungen von ICloneable spricht, ist es nicht wirklich Sache des Objekts selbst, zu entscheiden, ob ein flacher oder tiefer Klon oder sogar ein teilweise flacher / teilweise tiefer Klon durchgeführt werden soll? Sollte es uns wirklich etwas ausmachen, solange das Objekt wie beabsichtigt funktioniert? In einigen Fällen kann eine gute Klonimplementierung sowohl flaches als auch tiefes Klonen umfassen.
quelle