C'è un motivo particolare per cui ICloneable<T>
non esiste un generico ?
Sarebbe molto più comodo, se non avessi bisogno di lanciarlo ogni volta che clonavo qualcosa.
C'è un motivo particolare per cui ICloneable<T>
non esiste un generico ?
Sarebbe molto più comodo, se non avessi bisogno di lanciarlo ogni volta che clonavo qualcosa.
Risposte:
ICloneable è ora considerata una cattiva API, poiché non specifica se il risultato è una copia profonda o superficiale. Penso che sia per questo che non migliorano questa interfaccia.
Probabilmente puoi fare un metodo di estensione della clonazione tipizzato, ma penso che richiederebbe un nome diverso poiché i metodi di estensione hanno meno priorità rispetto a quelli originali.
List<T>
avesse un metodo clone, mi aspetterei che producesse un i List<T>
cui elementi hanno le stesse identità di quelli dell'elenco originale, ma mi aspetto che qualsiasi struttura di dati interna venga duplicata in base alle necessità per garantire che nulla influisca su un elenco le identità degli oggetti archiviati nell'altro. Dov'è l'ambiguità? Un problema più grande con la clonazione arriva con una variazione del "problema dei diamanti": se CloneableFoo
eredita da [non clonabile pubblicamente] Foo
, dovrebbe CloneableDerivedFoo
derivare da ...
identity
elenco dell'elenco stesso, ad esempio (nel caso di un elenco di elenchi)? Tuttavia, ignorando ciò, le tue aspettative non sono l'unica idea possibile che le persone possano avere quando chiamano o implementano Clone
. Che cosa succede se gli autori delle biblioteche che implementano un altro elenco non seguono le tue aspettative? L'API dovrebbe essere banalmente inequivocabile, non discutibilmente inequivocabile.
Clone
tutte le sue parti, non funzionerà in modo prevedibile, a seconda che quella parte sia stata implementata da te o da quella persona a chi piace la clonazione profonda. il tuo punto sui modelli è valido ma avere IMHO nell'API non è abbastanza chiaro - o dovrebbe essere chiamato ShallowCopy
per sottolineare il punto, o non fornito affatto.
Oltre alla risposta di Andrey (che sono d'accordo con, +1) - quando ICloneable
è fatto, si può anche scegliere esplicito implementazione per rendere il pubblico Clone()
di ritorno un oggetto tipizzato:
public Foo Clone() { /* your code */ }
object ICloneable.Clone() {return Clone();}
Naturalmente c'è un secondo problema con una ICloneable<T>
eredità generica .
Se ho:
public class Foo {}
public class Bar : Foo {}
E ho implementato ICloneable<T>
, quindi ho implementato ICloneable<Foo>
? ICloneable<Bar>
? Inizi rapidamente a implementare molte interfacce identiche ... Confronta con un cast ... ed è davvero così male?
Devo chiederti, cosa faresti esattamente con l'interfaccia oltre a implementarla? Le interfacce sono in genere utili solo quando si esegue il cast su di essa (ad esempio, questa classe supporta "IBar") o hanno parametri o setter che la accettano (ovvero, prendo una "IBar"). Con ICloneable - abbiamo esaminato l'intero Framework e non siamo riusciti a trovare un singolo utilizzo ovunque che fosse qualcosa di diverso da una sua implementazione. Inoltre, non siamo riusciti a trovare alcun utilizzo nel "mondo reale" che faccia qualcosa di diverso dall'implementarlo (nelle ~ 60.000 app a cui abbiamo accesso).
Ora, se vuoi solo applicare un modello che desideri implementare i tuoi oggetti "clonabili", è un uso del tutto perfetto - e vai avanti. Puoi anche decidere esattamente cosa significa "clonazione" per te (cioè profondo o superficiale). Tuttavia, in tal caso, non è necessario per noi (il BCL) definirlo. Definiamo astrazioni nel BCL solo quando è necessario scambiare istanze tipizzate come tale astrazione tra librerie non correlate.
David Kean (BCL Team)
ICloneable<out T>
potrebbe essere molto utile se ereditato da ISelf<out T>
, con un solo metodo Self
di tipo T
. Uno spesso non ha bisogno di "qualcosa che sia clonabile", ma potrebbe benissimo aver bisogno T
di qualcosa che sia clonabile. Se un oggetto clonabile implementa ISelf<itsOwnType>
, una routine che necessita di un T
clonabile può accettare un parametro di tipo ICloneable<T>
, anche se non tutti i derivati clonabili T
condividono un antenato comune.
ICloneable<T>
potrebbe essere utile per questo, sebbene un quadro più ampio per il mantenimento di classi mutabili e immutabili parallele potrebbe essere più utile. In altre parole, il codice che ha bisogno di vedere che cosa Foo
contiene un certo tipo ma non lo cambierà né si aspetta che non cambierà mai potrebbe usare un IReadableFoo
, mentre ...
Foo
potrebbe usare un ImmutableFoo
codice while che per manipolarlo potrebbe usare un MutableFoo
. Il codice fornito con qualsiasi tipo di IReadableFoo
dovrebbe essere in grado di ottenere una versione mutabile o immutabile. Un tale quadro sarebbe bello, ma sfortunatamente non riesco a trovare un modo carino per sistemare le cose in modo generico. Se esistesse un modo coerente per creare un wrapper di sola lettura per una classe, una cosa del genere potrebbe essere utilizzata in combinazione con ICloneable<T>
una copia immutabile di una classe che contiene T
".
List<T>
, in modo tale che il clonato List<T>
sia una nuova raccolta che trattiene i puntatori a tutti gli stessi oggetti nella raccolta originale, ci sono due semplici modi per farlo senza ICloneable<T>
. Il primo è il Enumerable.ToList()
metodo di estensione: List<foo> clone = original.ToList();
il secondo è il List<T>
costruttore che accetta un IEnumerable<T>
: List<foo> clone = new List<foo>(original);
sospetto che il metodo di estensione stia probabilmente chiamando il costruttore, ma entrambi faranno ciò che stai richiedendo. ;)
Penso che la domanda "perché" sia inutile. Esistono molte interfacce / classi / ecc ... che sono molto utili, ma non fanno parte della libreria di base di .NET Frameworku.
Ma soprattutto puoi farlo da solo.
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(); }
}
È abbastanza facile scrivere l'interfaccia da soli se ne hai bisogno:
public interface ICloneable<T> : ICloneable
where T : ICloneable<T>
{
new T Clone();
}
Avendo letto di recente l'articolo Perché copiare un oggetto è una cosa terribile da fare? , Penso che questa domanda abbia bisogno di ulteriori clafirication. Altre risposte qui forniscono buoni consigli, ma la risposta non è completa - perché no ICloneable<T>
?
uso
Quindi, hai una classe che la implementa. Mentre prima avevi un metodo che desiderava ICloneable
, ora deve essere generico da accettare ICloneable<T>
. Dovresti modificarlo.
Quindi, potresti avere un metodo che controlla se un oggetto is ICloneable
. E adesso? Non puoi farlo is ICloneable<>
e poiché non conosci il tipo di oggetto al tipo di compilazione, non puoi rendere generico il metodo. Primo vero problema.
Quindi devi avere entrambi ICloneable<T>
e ICloneable
, il primo implementando il secondo. Quindi un implementatore dovrebbe implementare entrambi i metodi - object Clone()
e T Clone()
. No, grazie, ci siamo già già divertiti abbastanza IEnumerable
.
Come già sottolineato, esiste anche la complessità dell'eredità. Mentre la covarianza può sembrare risolvere questo problema, un tipo derivato deve implementare ICloneable<T>
il proprio tipo, ma esiste già un metodo con la stessa firma (= parametri, in sostanza) - quello Clone()
della classe base. Rendere esplicita la tua nuova interfaccia del metodo clone è inutile, perderai il vantaggio che hai cercato durante la creazione ICloneable<T>
. Quindi aggiungi la new
parola chiave. Ma non dimenticare che dovresti anche sovrascrivere la classe base ' Clone()
(l'implementazione deve rimanere uniforme per tutte le classi derivate, cioè restituire lo stesso oggetto da ogni metodo clone, quindi deve essere il metodo clone base virtual
)! Ma, sfortunatamente, non è possibile entrambioverride
enew
metodi con la stessa firma. Scegliendo la prima parola chiave, si perderebbe l'obiettivo che si desidera avere quando si aggiunge ICloneable<T>
. Lanciando il secondo, si romperà l'interfaccia stessa, facendo metodi che dovrebbero fare lo stesso restituire oggetti diversi.
Punto
Desiderate il ICloneable<T>
comfort, ma il comfort non è ciò per cui sono progettate le interfacce, il loro significato è (in generale OOP) unificare il comportamento degli oggetti (sebbene in C # sia limitato all'unificazione del comportamento esteriore, ad esempio i metodi e le proprietà, non il loro funzionamento).
Se il primo motivo non ti ha ancora convinto, potresti obiettare che ICloneable<T>
potrebbe funzionare anche in modo restrittivo, per limitare il tipo restituito dal metodo clone. Tuttavia, il programmatore cattivo può implementare ICloneable<T>
dove T non è il tipo che lo sta implementando. Quindi, per ottenere la tua restrizione, puoi aggiungere un bel vincolo al parametro generico:
public interface ICloneable<T> : ICloneable where T : ICloneable<T>
Certamente più restrittivo di quello senza where
, non puoi ancora limitare che T è il tipo che sta implementando l'interfaccia (puoi derivare da ICloneable<T>
un tipo diverso che lo implementa).
Vedete, anche questo scopo non può essere raggiunto (anche l'originale ICloneable
fallisce, nessuna interfaccia può davvero limitare il comportamento della classe di implementazione).
Come puoi vedere, questo dimostra che rendere l'interfaccia generica sia difficile da implementare completamente, ma anche non necessaria e inutile.
Ma tornando alla domanda, quello che cerchi veramente è avere conforto durante la clonazione di un oggetto. Ci sono due modi per farlo:
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();
}
}
Questa soluzione offre agli utenti comfort e comportamento previsto, ma è anche troppo lunga da implementare. Se non volessimo che il metodo "comodo" restituisse il tipo corrente, è molto più facile averlo public virtual object Clone()
.
Vediamo quindi la soluzione "definitiva": cosa in C # è veramente intenzionato a darci conforto?
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;
}
Si chiama Copia per non scontrarsi con gli attuali metodi Clone (il compilatore preferisce i metodi dichiarati del tipo rispetto a quelli di estensione). Il class
vincolo è lì per la velocità (non richiede controllo null ecc.).
Spero che questo chiarisca il motivo per cui non farlo ICloneable<T>
. Tuttavia, si consiglia di non implementare ICloneable
affatto.
ICloneable
è per i tipi di valore, in cui potrebbe eludere il boxing del metodo Clone e implica che il valore non sia unbox. E poiché le strutture possono essere clonate (in modo superficiale) automaticamente, non è necessario implementarle (a meno che non si specifichi che significa copia profonda).
Sebbene la domanda sia molto vecchia (a 5 anni dalla stesura di questa risposta :) ed è già stata risolta, ma ho trovato questo articolo che risponde abbastanza bene alla domanda, controlla qui
MODIFICARE:
Ecco la citazione dall'articolo che risponde alla domanda (assicurati di leggere l'articolo completo, include altre cose interessanti):
Ci sono molti riferimenti su Internet che rimandano a un post del blog 2003 di Brad Abrams - all'epoca impiegato in Microsoft - in cui vengono discusse alcune considerazioni su ICloneable. La voce del blog è disponibile a questo indirizzo: Implementazione di ICloneable . Nonostante il titolo fuorviante, questo post sul blog invita a non implementare ICloneable, principalmente a causa della confusione superficiale / profonda. L'articolo termina con un semplice suggerimento: se hai bisogno di un meccanismo di clonazione, definisci la tua metodologia Clone o Copy e assicurati di documentare chiaramente se si tratta di una copia profonda o superficiale. Un modello appropriato è:
public <type> Copy();
Un grosso problema è che non potevano limitare T alla stessa classe. Per esempio cosa ti impedirebbe di fare questo:
interface IClonable<T>
{
T Clone();
}
class Dog : IClonable<JackRabbit>
{
//not what you would expect, but possible
JackRabbit Clone()
{
return new JackRabbit();
}
}
Hanno bisogno di una limitazione dei parametri come:
interfact IClonable<T> where T : implementing_type
class A : ICloneable { public object Clone() { return 1; } /* I can return whatever I want */ }
ICloneable<T>
potesse costringere T
ad abbinare il proprio tipo, ciò non costringerebbe un'implementazione di Clone()
restituire qualcosa di remoto simile all'oggetto su cui è stato clonato. Inoltre, suggerirei che se si utilizza la covarianza di interfaccia, potrebbe essere meglio avere classi che implementano ICloneable
essere sigillate, che l'interfaccia ICloneable<out T>
includa una Self
proprietà che dovrebbe restituire se stessa e ...
ICloneable<BaseType>
o ICloneable<ICloneable<BaseType>>
. Il BaseType
in questione dovrebbe avere un protected
metodo per la clonazione, che sarebbe chiamato dal tipo che implementa ICloneable
. Questo design consentirebbe la possibilità che si possa desiderare di avere a Container
, a CloneableContainer
, a FancyContainer
e a CloneableFancyContainer
, essendo quest'ultimo utilizzabile nel codice che richiede una derivazione clonabile Container
o che richiede un FancyContainer
(ma non gli importa se è clonabile).
FancyList
tipo che potrebbe essere clonato in modo ragionevole, ma un derivato potrebbe automaticamente mantenere il suo stato in un file su disco (specificato nel costruttore). Il tipo derivato non poteva essere clonato, perché il suo stato sarebbe essere collegato a quello di un Singleton mutabile (il file), ma che l'uso non dovrebbero precludere del tipo derivato in posti che hanno bisogno la maggior parte delle caratteristiche di una FancyList
, ma non avrebbe avuto bisogno per clonarlo.
È un'ottima domanda ... Puoi crearne una tua, però:
interface ICloneable<T> : ICloneable
{
new T Clone ( );
}
Andrey dice che è considerata una cattiva API, ma non ho sentito nulla su questa interfaccia diventare deprecata. E ciò spezzerebbe tonnellate di interfacce ... Il metodo Clone dovrebbe eseguire una copia superficiale. Se l'oggetto fornisce anche una copia approfondita, è possibile utilizzare un Clone sovraccarico (profondità bool).
EDIT: Pattern che uso per "clonare" un oggetto, sta passando un prototipo nel costruttore.
class C
{
public C ( C prototype )
{
...
}
}
Ciò rimuove qualsiasi potenziale situazione di implementazione del codice ridondante. A proposito, parlando dei limiti di ICloneable, non sta davvero all'oggetto stesso decidere se eseguire un clone superficiale o profondo, o anche un clone parzialmente superficiale / parzialmente profondo? Dovremmo davvero preoccuparci, purché l'oggetto funzioni come previsto? In alcune occasioni, una buona implementazione di Clone potrebbe benissimo includere sia la clonazione superficiale che quella profonda.