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.
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.
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.
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 es CloneableFoo
von [nicht öffentlich klonbar] geerbt wird Foo
, sollte es CloneableDerivedFoo
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önnen Clone
. 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 werden ShallowCopy
, 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 machen Clone()
Rückkehr ein typisierte Objekt:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Natürlich gibt es ein zweites Problem mit einer generischen ICloneable<T>
Vererbung.
Wenn ich habe:
public class Foo {}
public class Bar : Foo {}
Und ich habe implementiert ICloneable<T>
, dann implementiere ich ICloneable<Foo>
? ICloneable<Bar>
? Sie beginnen schnell mit der Implementierung vieler identischer Schnittstellen ... Vergleichen Sie mit einer Besetzung ... und ist es wirklich so schlimm?
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)
ICloneable<out T>
könnte sehr nützlich sein , wenn es von geerbt ISelf<out T>
, mit einer einzigen Methode Self
des Typs T
. Man braucht nicht oft "etwas, das klonbar ist", aber man braucht sehr gut etwas T
, das klonbar ist. Wenn ein klonbares Objekt implementiert wird ISelf<itsOwnType>
, kann eine Routine, die ein klonbares Objekt benötigt T
, einen Parameter vom Typ akzeptieren ICloneable<T>
, auch wenn nicht alle klonbaren Ableitungen von T
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 von Foo
enthält, aber weder mutiert noch erwartet, dass er sich nie ändern wird, könnte eine IReadableFoo
, während ...
Foo
könnte einen ImmutableFoo
while-Code verwenden, der manipuliert werden soll, könnte einen verwenden MutableFoo
. Code für jede Art von IReadableFoo
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 mit ICloneable<T>
einer unveränderlichen Kopie einer Klasse verwendet werden, die T
'enthält.
List<T>
, sodass das Klonen eine List<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 tun ICloneable<T>
. Die erste ist die Enumerable.ToList()
Erweiterungsmethode: List<foo> clone = original.ToList();
Die zweite ist der List<T>
Konstruktor, der Folgendes verwendet IEnumerable<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.
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(); }
}
Es ist ziemlich einfach , die Benutzeroberfläche selbst zu schreiben, wenn Sie sie benötigen:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
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 werden ICloneable<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>
und ICloneable
das erstere muss das letztere implementieren. Ein Implementierer müsste also beide Methoden implementieren - object Clone()
und T Clone()
. Nein, danke, wir haben schon genug Spaß damit IEnumerable
.
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) - die Clone()
der Basisklasse. Es ist sinnlos, Ihre neue Klonmethodenschnittstelle explizit zu machen. Sie verlieren den Vorteil, den Sie beim Erstellen gesucht haben ICloneable<T>
. Fügen Sie also das new
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 sein virtual
)! Aber leider kann man nicht beides override
undnew
Methoden mit der gleichen Signatur. Wenn Sie das erste Keyword auswählen, verlieren Sie das Ziel, das Sie beim Hinzufügen haben möchten ICloneable<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önnen ICloneable<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:
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();
}
}
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?
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;
}
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 implementieren ICloneable
.
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):
Es gibt viele Referenzen im Internet, die auf einen Blog-Beitrag von Brad Abrams aus dem Jahr 2003 verweisen - zu der Zeit bei Microsoft -, in dem einige Gedanken zu ICloneable diskutiert werden. Der Blogeintrag befindet sich unter folgender Adresse: Implementierung von ICloneable . Trotz des irreführenden Titels fordert dieser Blogeintrag, ICloneable nicht zu implementieren, hauptsächlich wegen flacher / tiefer Verwirrung. Der Artikel endet mit einem klaren Vorschlag: Wenn Sie einen Klonmechanismus benötigen, definieren Sie Ihre eigene Klon- oder Kopiermethode und stellen Sie sicher, dass Sie klar dokumentieren, ob es sich um eine tiefe oder flache Kopie handelt. Ein geeignetes Muster ist:
public <type> Copy();
Ein großes Problem ist, dass sie T nicht auf dieselbe Klasse beschränken konnten. Zum Beispiel, was Sie daran hindern würde:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Sie benötigen eine Parametereinschränkung wie:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
sich darauf beschränken könnte T
, seinem eigenen Typ zu entsprechen, würde dies eine Implementierung von nicht zwingen Clone()
, 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, Klassen ICloneable
zu versiegeln, die implementiert werden, die Schnittstelle ICloneable<out T>
eine Self
Eigenschaft enthält, von der erwartet wird, dass sie sich selbst
ICloneable<BaseType>
oder ICloneable<ICloneable<BaseType>>
. Die BaseType
fragliche sollte eine protected
Methode zum Klonen haben, die von dem Typ aufgerufen wird, der implementiert ICloneable
. Dieses Design würde die Möglichkeit ermöglichen, dass man a Container
, a CloneableContainer
, a FancyContainer
und a haben möchte CloneableFancyContainer
, wobei letzteres in Code verwendbar ist, der eine klonbare Ableitung von Container
oder erfordert a FancyContainer
(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 eines FancyList
benötigen, aber nicht benötigen um es zu klonen.
Es ist eine sehr gute Frage ... Sie könnten jedoch Ihre eigene machen:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
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.
class C
{
public C ( C prototype )
{
...
}
}
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.