Questa difficoltà si manifesta poiché quasi sempre gli oggetti riassegnati vengono legati all'oggetto sorgente per riferimento*.
Per risolvere questo noioso problema solitamente nei nostri progetti implementiamo dei metodi di "Clone" ossia dei metodi che ci copiano gli oggetti ( veriabili, classi, e quant'altro ) su un altro contenitore non collegato al primo.
A volte però ci sono oggetti per cui abbiamo grosse difficoltà perché magari non sono serializzabili o altro ... ma ecco che vi posto un po di metodologie che vi aiuteranno nella quasi totalità dei casi di Clonazione.
IL (Intermediate Language) può essere utilizzato per clonare oggetti e non è affatto male, e può essere abbastanza performante.
definiamo una Classe Person :
public class Person { private int _id; private string _name; private string _firstName; private string _field1, _field2, _field3; public Person() { } public int ID { get { return _id; } set { _id = value ; } } public string Name { get { return _name; } set { _name = value ; } } public string FirstName { get { return _firstName; } set { _firstName = value ; } } }
Test Code: Il codice riportato di seguito è un bel pezzo di codice, leggete i commenti e capirete cosa vi è dichiarato.
class Program { /// <summary> /// Delegate handler that's used to compile the IL to. /// (This delegate is standard in .net 3.5) /// </summary> /// <typeparam name="T1">Parameter Type</typeparam> /// <typeparam name="TResult">Return Type</typeparam> /// <param name="arg1">Argument</param> /// <returns>Result</returns> public delegate TResult Func<T1, TResult>(T1 arg1); /// <summary> /// This dictionary caches the delegates for each 'to-clone' type. /// </summary> static Dictionary<Type, Delegate> _cachedIL = new Dictionary<Type, Delegate>(); /// <summary> /// The Main method that's executed for the test. /// </summary> /// <param name="args"></param> static void Main( string [] args) { DoCloningTest(1000); DoCloningTest(10000); DoCloningTest(100000); //Commented because the test takes long ;) //DoCloningTest(1000000); Console.ReadKey(); } /// <summary> /// Do the cloning test and printout the results. /// </summary> /// <param name="count">Number of items to clone</param> private static void DoCloningTest( int count) { // Create timer class. HiPerfTimer timer = new HiPerfTimer(); double timeElapsedN = 0, timeElapsedR = 0, timeElapsedIL = 0; Console.WriteLine( "--> Creating {0} objects..." , count); timer.StartNew(); List<Person> personsToClone = CreatePersonsList(count); timer.Stop(); Person temp = CloneObjectWithIL(personsToClone[0]); temp = null ; Console.WriteLine( "\tCreated objects in {0} seconds" , timer.Duration); Console.WriteLine( "- Cloning Normal..." ); List<Person> clonedPersons = new List<Person>(count); timer.StartNew(); foreach (Person p in personsToClone) { clonedPersons.Add(CloneNormal(p)); } timer.Stop(); timeElapsedN = timer.Duration; Console.WriteLine( "- Cloning IL..." ); clonedPersons = new List<Person>(count); timer.StartNew(); foreach (Person p in personsToClone) { clonedPersons.Add(CloneObjectWithIL<Person>(p)); } timer.Stop(); timeElapsedIL = timer.Duration; Console.WriteLine( "- Cloning Reflection..." ); clonedPersons = new List<Person>(count); timer.StartNew(); foreach (Person p in personsToClone) { clonedPersons.Add(CloneObjectWithReflection(p)); } timer.Stop(); timeElapsedR = timer.Duration; Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine( "----------------------------------------" ); Console.WriteLine( "Object count:\t\t{0}" , count); Console.WriteLine( "Cloning Normal:\t\t{0:00.0000} s" , timeElapsedN); Console.WriteLine( "Cloning IL:\t\t{0:00.0000} s" , timeElapsedIL); Console.WriteLine( "Cloning Reflection:\t{0:00.0000} s" , timeElapsedR); Console.WriteLine( "----------------------------------------" ); Console.ResetColor(); } /// <summary> /// Create a list of persons with random data and a given number of items. /// </summary> /// <param name="count">Number of persons to generate</param> /// <returns>List of generated persons</returns> private static List<Person> CreatePersonsList( int count) { Random r = new Random(Environment.TickCount); List<Person> persons = new List<Person>(count); for ( int i = 0; i < count; i++) { Person p = new Person(); p.ID = r.Next(); p.Name = string .Concat( "Slaets_" , r.Next()); p.FirstName = string .Concat( "Filip_" , r.Next()); persons.Add(p); } return persons; } /// <summary> /// Clone one person object with reflection /// </summary> /// <param name="p">Person to clone</param> /// <returns>Cloned person</returns> private static Person CloneObjectWithReflection(Person p) { // Get all the fields of the type, also the privates. FieldInfo[] fis = p.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); // Create a new person object Person newPerson = new Person(); // Loop through all the fields and copy the information from the parameter class // to the newPerson class. foreach (FieldInfo fi in fis) { fi.SetValue(newPerson, fi.GetValue(p)); } // Return the cloned object. return newPerson; } /// <summary> /// Generic cloning method that clones an object using IL. /// Only the first call of a certain type will hold back performance. /// After the first call, the compiled IL is executed. /// </summary> /// <typeparam name="T">Type of object to clone</typeparam> /// <param name="myObject">Object to clone</param> /// <returns>Cloned object</returns> private static T CloneObjectWithIL<T>(T myObject) { Delegate myExec = null ; if (!_cachedIL.TryGetValue( typeof (T), out myExec)) { // Create ILGenerator DynamicMethod dymMethod = new DynamicMethod( "DoClone" , typeof (T), new Type[] { typeof (T) }, true ); ConstructorInfo cInfo = myObject.GetType().GetConstructor( new Type[] { }); ILGenerator generator = dymMethod.GetILGenerator(); LocalBuilder lbf = generator.DeclareLocal( typeof (T)); //lbf.SetLocalSymInfo("_temp"); generator.Emit(OpCodes.Newobj, cInfo); generator.Emit(OpCodes.Stloc_0); foreach (FieldInfo field in myObject.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)) { // Load the new object on the eval stack... (currently 1 item on eval stack) generator.Emit(OpCodes.Ldloc_0); // Load initial object (parameter) (currently 2 items on eval stack) generator.Emit(OpCodes.Ldarg_0); // Replace value by field value (still currently 2 items on eval stack) generator.Emit(OpCodes.Ldfld, field); // Store the value of the top on the eval stack into the object underneath that value on the value stack. // (0 items on eval stack) generator.Emit(OpCodes.Stfld, field); } // Load new constructed obj on eval stack -> 1 item on stack generator.Emit(OpCodes.Ldloc_0); // Return constructed object. --> 0 items on stack generator.Emit(OpCodes.Ret); myExec = dymMethod.CreateDelegate( typeof (Func<T, T>)); _cachedIL.Add( typeof (T), myExec); } return ((Func<T, T>)myExec)(myObject); } /// <summary> /// Clone a person object by manually typing the copy statements. /// </summary> /// <param name="p">Object to clone</param> /// <returns>Cloned object</returns> private static Person CloneNormal(Person p) { Person newPerson = new Person(); newPerson.ID = p.ID; newPerson.Name = p.Name; newPerson.FirstName = p.FirstName; return newPerson; } }
La cosa fondamentale che fa è, creare un metodo dinamico, ottenere il ILGenerator, creare codice nel metodo, compilarlo per un delegato, ed eseguire il delegato.
Il delegato è in cache cosi che l'IL non viene generato ogni volta che una clonazione dovrebbe avvenire, quindi perdiamo un solo poco tempo, quando il primo oggetto viene clonato (IL deve essere creato e compilato in fase di esecuzione).
Speriamo che questo articolo sia utile per alcune persone se è così, fatemelo sapere.
Saluti
Nessun commento:
Posta un commento