Una chiarissima discussione sull'argomento si è già sviluppata su blog di Brad Abrams:
http://blogs.msdn.com/brada/archive/2004/05/03/125427.aspx
In tanti sostengono la necessità di eseguire una deep copy, cioè la copia dell'oggetto e di tutto il grafo che sta sotto l'oggetto stesso (se l'oggetto A mantiene una collection di oggetti B, la deep copia anche tutti i B ed eventuali ulteriori ramificazioni).
La shallow copy è invece la copia del solo oggetto candidato alla copia, lasciando che l'oggetto e la sua copia abbiano dei reference che puntino ai medesimi sotto-oggetti.
In molti casi la deep è quantomeno ammaliante, perché così evito almeno incroci pericolosi di reference. Di primo acchito quindi concordo che la deep è la risposta migliore al problema in senso generale.
Inoltre eseguire una deep copy è complesso ma se non ci curiamo degli aspetti di performance, esiste una soluzione semplice, che richiede però che l'oggetto sia serializzabile:
public static object SerializeClone(object o)
{
IFormatter formatter = new BinaryFormatter();
using(Stream stream = new MemoryStream())
{
try
{
formatter.Serialize(stream, o);
stream.Seek(0, SeekOrigin.Begin);
return formatter.Deserialize(stream);
}
catch(SerializationException err)
{
Debug.WriteLine(err.ToString());
throw;
}
catch(Exception err)
{
Debug.WriteLine(err.ToString());
throw;
}
}
}
Da tempo sto continuando ad aggiungere e modificare funzionalità alla versione per il fx 2.0 della mia collection che porterò come esempio in una delle mie sessioni alla WPC 2006.
Della collection parlerò un'altra volta sul blog perché ho in mente un progetto interessante per la community.
Per supportare la transazionalità nella collection ho la necessità di clonare le entity. In sostanza sto parlando del supporto per AcceptChanges, RejectChanges, IsChanged.
Se l'oggetto supportasse la deep copy (fatta con il metodo generico citato sopra o anche uno ad-hoc) sarebbe un vero disastro.
In sostanza, vedrei clonato l'intero albero di dipendenza degli oggetti e rischierei di continuare a lavorare con la versione resa vecchia da una AcceptChanges.
Per esempio gli oggetti Order hanno una collection di OrderDetails. L'applicativo mostra in grid un Order mentre in un'altra parte del form ho dei reference attivi sugli OrderDetails per fare delle somme. A questo punto modifico una proprietà di Order, avviene la deep copy, ed eseguo la commit dei cambiamenti con AcceptChanges. I vecchi reference su OrderDetails sono sempre attivi e le modifiche su quegli oggetti sarebbero del tutto inutili a causa della deep copy.
In sostanza in questi casi la deep copy:
- è molto ma molto meno performante
- può portare a gravi errori
- è inutile allo scopo
Il terzo punto è quello più valido
In conclusione come spesso accade la risposta è "dipende", e riguardo al post di Brad Abrams, ritengo che sia impossibile anche scegliere tra deep e shallow, pur separando le interfacce. Sono certo che potremmo trovare dei casi in cui sia necessario un mix tra le due (cioè un ramo clonato deep, e altri non clonati).
O sviluppatore, pensaci tu ...