Area di riferimento
- Developing applications that use system types and collections
- Implement .NET Framework interfaces to cause components to comply with standard contracts. (Refer System namespace)
- ICloneable interface
Shallow copy e Deep Copy
Spesso nella pratica si ha la necessità di dover effettuare la copia di un oggetto.
Esistono sostanzialmente due possibilità:
La shallow copy effettua una copia parziale dell'oggetto, in particolare vengono copiati solamente i membri value-type mentre gli oggetti figli vengono condivisi con l'oggetto di partenza. In questo caso una modifica alla copia può comportare anche una modifica dell'oggetto da cui è stata costruita e ciò spesso non è un comportamento desiderato.
La deep copy invece comporta una copia integrale dell'oggetto. Il nuovo oggetto creato non ha più niente in comune con il padre.
ICloneable interface
L'interfaccia ICloneable definisce un solo metodo:
[ComVisible(true)]
public interface ICloneable
{
// Methods
object Clone();
}
Questo crea una certa confusione in quanto non permette di distinguere se l'implementazione eseguirà una shallow copy oppure una deep copy. E' importante che questa imformazione venga aggiunta alla documentazione. Facciamo un esempio utilizzando i seguenti oggetti:
public class Esame
{
private string _nome; // Nome dell'esame
private int _voto; // Voto ottenuto dallo studente
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
public int Voto
{
get { return _voto; }
set { _voto = value; }
}
public Esame(string nome, int voto)
{
_nome = nome;
_voto = voto;
}
}
public class Studente
{
private int _matricola;
private string _nome;
private string _cognome;
private List<Esame> _esami; // Esami sostenuti dallo studente
public Studente(int matricola, string nome, string cognome)
{
_matricola = matricola;
_nome = nome;
_cognome = cognome;
_esami = new List<Esame>();
}
public Studente(int matricola, string nome, string cognome, List<Esame> esami) : this(matricola, nome, cognome)
{
_esami = esami;
}
public int Matricola
{
get { return _matricola; }
set { _matricola = value; }
}
public string Nome
{
get { return _nome; }
set { _nome = value; }
}
public string Cognome
{
get { return _cognome; }
set { _cognome = value; }
}
public List<Esame> Esami
{
get { return _esami; }
set { _esami = value; }
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine(String.Format("Studente {0} {1} matricola {2} ha sostenuto i seguenti esami:", _nome, _cognome, _matricola));
foreach(Esame esame in _esami)
{
sb.AppendLine(String.Format(" - {0}: {1}", esame.Nome, esame.Voto));
}
return sb.ToString();
}
}
Uno Studente è caratterizzato da un numero di matricola, un nome, un cognome e un elenco degli esami che ha sostenuto con il relativo voto.
Vediamo come implementare l'interfaccia ICloneable in Studente affinchè venga effettuata una shallow copy:
public class Studente : ICloneable
{
......
// Effettua una Shallow Copy dell'oggetto Studente
public object Clone()
{
return this.MemberwiseClone();
}
}
Per realizzare una shallow copy è quindi sufficiente richiamare il metodo protected MemberwiseClone() appartenente al tipo Object.
Verifichiamo adesso che è stata effettivamente realizzata una shallow copy:
Studente andrea = new Studente(111111, "Andrea", "Angella");
andrea.Esami.Add(new Esame("Fondamenti di Informatica", 27));
andrea.Esami.Add(new Esame("Matematica", 25));
// Effettuo una shallow copy
Studente copia = (Studente) andrea.Clone();
// Modifica della copia per vedere quali campi sono in comune con il padre
copia.Matricola = 222222;
copia.Nome = "Stefano";
copia.Cognome = "D'Onofrio";
copia.Esami[0].Nome = "Calcolatori Elettronici";
copia.Esami[1].Voto = 30;
copia.Esami.Add(new Esame("Elettrotecnica", 19));
// Visualizzo le informazioni sugli studenti
Console.WriteLine(andrea.ToString());
Console.WriteLine(copia.ToString());
Console.ReadKey();
L'output del precedente spezzone di codice è il seguente:
Studente Andrea Angella matricola 111111 ha sostenuto i seguenti esami:
- Calcolatori Elettronici: 27
- Matematica: 30
- Elettrotecnica: 19
Studente Stefano D'Onofrio matricola 222222 ha sostenuto i seguenti esami:
- Calcolatori Elettronici: 27
- Matematica: 30
- Elettrotecnica: 19
Vediamo come implementare l'interfaccia ICloneable in Studente affinchè venga effettuata una deep copy:
[Serializable]
public class Esame
{
.....
}
[Serializable]
public class Studente : ICloneable
{
.....
// Effettua una Deep Copy dell'oggetto Studente
public object Clone()
{
MemoryStream ms = new MemoryStream();
BinaryFormatter bf = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
// Serializza l'oggetto nello stream in memoria
bf.Serialize(ms, this);
// Costruisce un altro oggetto deserializzando i dati nello stream in memoria
ms.Position = 0;
object clone = bf.Deserialize(ms);
ms.Close();
return clone;
}
}
Per realizzare una deep copy è possibile sfruttare le tecniche di serializzazione e deserializzazione degli oggetti in memoria. Quando ciò è possibile, questa è una semplice implementazione riusabile.
Verifichiamo adesso che è stata effettivamente realizzata una deep copy analizzando l'output dello stesso spezzone di codice usato per verificare la shallow copy:
Studente Andrea Angella matricola 111111 ha sostenuto i seguenti esami:
- Fondamenti di Informatica: 27
- Matematica: 25
Studente Stefano D'Onofrio matricola 222222 ha sostenuto i seguenti esami:
- Calcolatori Elettronici: 27
- Matematica: 30
- Elettrotecnica: 19
Siccome la necessità di realizzare una deep copy è più alta, io preferisco utilizzare l'interfaccia ICloneable con questa semantica.
Nel caso fosse anche necessaria la possiblità di eseguire una shallow copy allora costruisco una interfaccia personalizzata IShallowCopy e la implemento.