Warum kein ICloneable <T>?

222

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.

Bluenuance
quelle
6
Nein! Bei allem Respekt vor den "Gründen" stimme ich Ihnen zu, sie hätten es umsetzen sollen!
Shimmy Weitzhandler
Es wäre schön gewesen, wenn Microsoft dies definiert hätte (das Problem bei eigenen Schnittstellen besteht darin, dass Schnittstellen in zwei verschiedenen Assemblys nicht kompatibel sind, selbst wenn sie semantisch identisch sind). Wenn ich die Schnittstelle entwerfen würde, hätte sie drei Mitglieder, Clone, Self und CloneIfMutable, die alle ein T zurückgeben würden (das letzte Mitglied würde je nach Bedarf entweder Clone oder Self zurückgeben). Das Self-Mitglied würde es ermöglichen, ein ICloneable (von Foo) als Parameter zu akzeptieren und es dann als Foo zu verwenden, ohne dass eine Typumwandlung erforderlich ist.
Supercat
Dies würde eine ordnungsgemäße Klonierungsklassenhierarchie ermöglichen, bei der vererbbare Klassen eine geschützte "Klon" -Methode verfügbar machen und Ableitungen versiegelt haben, die eine öffentliche Klasse verfügbar machen. Zum Beispiel könnte man Pile, CloneablePile: Pile, EnhancedPile: Pile und CloneableEnhancedPile: EnhancedPile haben, von denen keines beim Klonen beschädigt werden würde (obwohl nicht alle eine öffentliche Klonmethode verfügbar machen), und FurtherEnhancedPile: EnhancedPile (das würde beschädigt werden) wenn geklont, aber keine Klonmethode verfügbar gemacht). Eine Routine, die eine ICloneable (von Pile) akzeptiert, könnte eine CloneablePile oder eine CloneableEnhancedPile akzeptieren ...
Supercat
... obwohl CloneableEnhancedPile nicht von CloneablePile erbt. Beachten Sie, dass FurtherEnhancedPile, wenn EnhancedPile von CloneablePile geerbt wird, eine öffentliche Klonmethode verfügbar machen müsste und an Code übergeben werden könnte, der das Klonen erwarten würde, was gegen das Liskov-Substitutability-Prinzip verstößt. Da CloneableEnhancedPile ICloneable (Of EnhancedPile) und implizit ICloneable (Of Pile) implementieren würde, könnte es an eine Routine übergeben werden, die eine klonbare Ableitung von Pile erwartet.
Supercat

Antworten:

111

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.

Andrey Shchekin
quelle
8
Ich bin anderer Meinung, schlecht oder nicht schlecht, es ist manchmal sehr nützlich für SHALLOW Clonning, und in diesen Fällen wird der Clone <T> wirklich benötigt, um unnötiges Boxen und Unboxing zu sparen.
Shimmy Weitzhandler
2
@AndreyShchekin: Was ist unklar über tiefes oder flaches Klonen? Wenn 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 CloneableFoovon [nicht öffentlich klonbar] geerbt wird Foo, sollte es CloneableDerivedFoovon ...
Supercat
1
@supercat: Ich kann nicht sagen, dass ich Ihre Erwartungen vollständig verstehe. Wie werden Sie beispielsweise eine identityListe 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.
Andrey Shchekin
2
@supercat Du sprichst also von einer flachen Kopie. An einer tiefen Kopie ist jedoch nichts grundlegend Falsches, beispielsweise wenn Sie eine reversible Transaktion für speicherinterne Objekte ausführen möchten. Ihre Erwartung basiert auf Ihrem Anwendungsfall, andere Personen haben möglicherweise andere Erwartungen. Wenn sie Ihre Objekte in ihre Objekte einfügen (oder umgekehrt), sind die Ergebnisse unerwartet.
Andrey Shchekin
5
@supercat Sie berücksichtigen nicht, dass es Teil von .NET Framework ist, nicht Teil Ihrer Anwendung. Wenn Sie also eine Klasse erstellen, die kein Deep-Cloning ausführt, und eine andere Klasse eine Klasse erstellt, die Deep-Cloning ausführt und Clonealle 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.
Andrey Shchekin
141

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?

Marc Gravell
quelle
10
+1 Ich dachte immer, dass das Kovarianzproblem der wahre Grund ist
Tamas Czinege
Hier gibt es ein Problem: Die explizite Schnittstellenimplementierung muss privat sein , was zu Problemen führen würde, wenn Foo irgendwann in ICloneable umgewandelt werden muss ... Vermisse ich hier etwas?
Joel in Gö
3
Mein Fehler: Es stellt sich heraus, dass die Methode, obwohl sie als privat definiert ist , aufgerufen wird, wenn Foo in IClonable umgewandelt wird (CLR über C # 2nd Ed, S.319). Scheint eine seltsame Designentscheidung zu sein, aber da ist sie. Foo.Clone () gibt also die erste Methode an, und ((ICloneable) Foo) .Clone () gibt die zweite Methode an.
Joel in Gö
Tatsächlich sind explizite Schnittstellenimplementierungen streng genommen Teil der öffentlichen API. Insofern sind sie öffentlich über Casting abrufbar.
Marc Gravell
8
Die vorgeschlagene Lösung scheint zunächst großartig zu sein ... bis Sie erkennen, dass in einer Klassenhierarchie 'public Foo Clone ()' virtuell sein und in abgeleiteten Klassen überschrieben werden soll, damit sie ihren eigenen Typ zurückgeben (und ihre eigenen Felder von klonen können) Kurs). Leider können Sie den Rückgabetyp bei einer Überschreibung nicht ändern (das erwähnte Kovarianzproblem). Sie schließen also wieder den Kreis zum ursprünglichen Problem - die explizite Implementierung bringt Ihnen nicht wirklich viel (außer der Rückgabe des Basistyps Ihrer Hierarchie anstelle von 'Objekt'), und Sie können den größten Teil Ihres Problems wieder übertragen Clone () ruft Ergebnisse auf. Yuck!
Ken Beckett
18

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)

David Kean
quelle
1
Nicht-generic ICloneable ist nicht sehr nützlich, aber ICloneable<out T>könnte sehr nützlich sein , wenn es von geerbt ISelf<out T>, mit einer einzigen Methode Selfdes 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 Tgemeinsam einen Vorfahren haben.
Supercat
Vielen Dank. Das ähnelt der oben erwähnten Besetzungssituation (dh 'unterstützt diese Klasse' IBar '). Leider haben wir nur sehr begrenzte und isolierte Szenarien entwickelt, in denen Sie tatsächlich die Tatsache nutzen würden, dass T klonbar ist. Haben Sie Situationen im Sinn?
David Kean
Eine Sache, die in .net manchmal umständlich ist, ist die Verwaltung veränderlicher Sammlungen, wenn der Inhalt einer Sammlung als Wert angesehen werden soll (dh ich möchte später wissen, welche Elemente sich jetzt in der Sammlung befinden). So etwas 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 Fooenthält, aber weder mutiert noch erwartet, dass er sich nie ändern wird, könnte eine IReadableFoo, während ...
Supercat
... Code, der den Inhalt von enthalten möchte, Fookönnte einen ImmutableFoowhile-Code verwenden, der manipuliert werden soll, könnte einen verwenden MutableFoo. Code für jede Art von IReadableFoosollte 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.
Supercat
@supercat Wenn Sie a klonen möchten 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. ;)
CptRobby
12

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(); }
}
TcKs
quelle
Jede funktionsfähige Klonmethode für eine vererbbare Klasse muss Object.MemberwiseClone als Ausgangspunkt verwenden (oder Reflektion verwenden), da sonst nicht garantiert werden kann, dass der Klon vom gleichen Typ wie das ursprüngliche Objekt ist. Ich habe ein ziemlich schönes Muster, wenn Sie interessiert sind.
Supercat
@supercat warum nicht eine Antwort geben?
Nawfal
"Es gibt viele Schnittstellen / Klassen / etc ... was sehr nützlich ist, aber nicht Teil der .NET Frameworku-Basisbibliothek ist." Können Sie uns Beispiele geben?
Krythic
1
@Krythic dh: Erweiterte Datenstrukturen wie B-Tree, Rot-Schwarz-Baum, Kreispuffer. In '09 gab es keine Tupel, generische schwache Referenz, gleichzeitige Sammlungen ...
TcKs
10

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();
}
Mauricio Scheffer
quelle
Ich habe einen Ausschnitt
eingefügt, da
2
Und wie genau soll diese Schnittstelle funktionieren? Damit das Ergebnis einer Klonoperation auf Typ T umgewandelt werden kann, muss das zu klonende Objekt von T abgeleitet werden. Es gibt keine Möglichkeit, eine solche Typbeschränkung einzurichten. Realistisch gesehen ist das einzige, was ich als Ergebnis der iCloneable-Rückgabe sehen kann, ein Typ iCloneable.
Supercat
@supercat: Ja, genau das gibt Clone () zurück: ein T, das ICloneable <T> implementiert. MbUnit verwendet diese Schnittstelle seit Jahren , also ja, es funktioniert. Werfen Sie einen Blick in die Implementierung von MbUnit.
Mauricio Scheffer
2
@ Mauricio Scheffer: Ich sehe, was los ist. Kein Typ implementiert tatsächlich iCloneable (von T). Stattdessen implementiert jeder Typ iCloneable (für sich). Das würde funktionieren, denke ich, obwohl alles, was es tut, darin besteht, einen Typecast zu verschieben. Es ist schade, dass es keine Möglichkeit gibt, ein Feld als Vererbungsbeschränkung und als Schnittstellenbeschränkung zu deklarieren. Es wäre hilfreich, wenn eine Klonmethode ein Objekt eines halbklonbaren Basistyps (Klonen nur als geschützte Methode verfügbar gemacht) zurückgibt, jedoch mit einer Einschränkung für klonbare Typen. Dies würde es einem Objekt ermöglichen, klonierbare Derivate von halbklonierbaren Derivaten eines Basistyps zu akzeptieren.
Supercat
@Mauricio Scheffer: Übrigens würde ich vorschlagen, dass ICloneable (von T) auch eine schreibgeschützte Self-Eigenschaft (vom Rückgabetyp T) unterstützt. Dies würde es einer Methode ermöglichen, ein ICloneable (von Foo) zu akzeptieren und es (über die Self-Eigenschaft) als Foo zu verwenden.
Supercat
9

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>?

  1. Verwendung

    Sie haben also eine Klasse, die sie implementiert. Während Sie zuvor eine gewünschte Methode hatten, ICloneablemuss 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 ICloneabledas 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 newSchlü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 overrideundnewMethoden 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.

  2. 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 ICloneableschlä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

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?

Erweiterungsmethoden!

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 classEinschrä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.

IllidanS4 will Monica zurück
quelle
Anhang 1: Die einzige mögliche Verwendung eines Generikums ICloneableist 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).
IllidanS4 will Monica
2

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();

Sameh Deabes
quelle
1

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
Sheamus
quelle
8
Das scheint nicht so schlimm wie class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
Nikola Novak
Ich sehe nicht wirklich, wo das Problem liegt. Keine Schnittstelle kann Implementierungen zu einem angemessenen Verhalten zwingen. Selbst wenn man 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 ICloneablezu versiegeln, die implementiert werden, die Schnittstelle ICloneable<out T>eine SelfEigenschaft enthält, von der erwartet wird, dass sie sich selbst
zurückgibt
... haben die Verbraucher werfen oder einschränken zu ICloneable<BaseType>oder ICloneable<ICloneable<BaseType>>. Die BaseTypefragliche sollte eine protectedMethode 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 FancyContainerund a haben möchte CloneableFancyContainer, wobei letzteres in Code verwendbar ist, der eine klonbare Ableitung von Containeroder erfordert a FancyContainer(aber es ist egal, ob es klonbar ist).
Supercat
Der Grund, warum ich ein solches Design bevorzugen würde, ist, dass die Frage, ob ein Typ sinnvoll geklont werden kann, etwas orthogonal zu anderen Aspekten ist. Zum Beispiel könnte man einen FancyListTyp 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 FancyListbenötigen, aber nicht benötigen um es zu klonen.
Supercat
+1 - Diese Antwort ist die einzige von denen, die ich hier gesehen habe und die die Frage wirklich beantwortet.
IllidanS4 will Monica
0

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.

Baretta
quelle
"schlecht" bedeutet nicht veraltet. Es bedeutet einfach "Sie sind besser dran, es nicht zu benutzen". Es ist nicht in dem Sinne veraltet, dass "wir die ICloneable-Schnittstelle in Zukunft entfernen werden". Sie ermutigen Sie lediglich, es nicht zu verwenden, und aktualisieren es nicht mit Generika oder anderen neuen Funktionen.
Jalf
Dennis: Siehe Marc's Antworten. Kovarianz- / Vererbungsprobleme machen diese Schnittstelle für jedes Szenario, das zumindest geringfügig kompliziert ist, so gut wie unbrauchbar.
Tamas Czinege
Ja, ich kann die Einschränkungen von ICloneable auf jeden Fall erkennen ... Ich muss das Klonen jedoch selten verwenden.
Baretta