Καλημέρα παιδιά.
Καιρό τώρα, ήθελα ένα standard τρόπο να έχω μια μέθοδο Clone() στα
αντικείμενά μου, η οποία με αυτόματο τρόπο θα μου δημιουργεί ένα clone
ενός instance μιας κλάσσης μου. Κατέληξα σε 2 τρόπους, καθένας με τα
καλά και τα κακά του, του οποίους παραθέτω εδώ, ως τροφή για σχόλια και
απόψεις ...
Τρόπος 1. Κάνω inherit απο την κλάσση CloneableObject, (
η οποία
δημιουργεί κλώνους ενός object graph, όπου τα 'παιδιά' είναι είτε
value types ή ClonableObject's κι αυτά ... )
public abstract
class CloneableObject
{
public object
Clone(){
// Get the 'running' type
Type childType
= this.GetType();//MethodInfo.GetCurrentMethod().ReflectedType;
// ok, create a new instance of the reflected type
// get the default no-args constructor first, and then
invoke
ConstructorInfo
constructor = childType.GetConstructor(new
Type[0]);
object _instance = constructor.Invoke(null);
// get references to all the properties and fields, and
call each one
PropertyInfo[]
properties = childType.GetProperties();
FieldInfo[]
fields = childType.GetFields();
// cool so far .. now loop through the properties &
fields, get the values of 'this', and
// set the values to the new instance... phew !
foreach(PropertyInfo pInfo in
properties){
object pValue = pInfo.GetValue(this, null);
object thePValue = (pValue is
CloneableObject)? ((CloneableObject)pValue).Clone() : pValue;
pInfo.SetValue(_instance,
thePValue, null);
}
foreach(FieldInfo fInfo in
fields){
object fValue = fInfo.GetValue(this);
object theFValue = (fValue is
CloneableObject)? ((CloneableObject)fValue).Clone() : fValue;
fInfo.SetValue(_instance,
theFValue);
}
return
_instance;
}
}
Τρόπος 2. Κάνω inherit απ'την κλάσση BinaryCloneableObject, η οποία
χρησιμοποιεί Binary Serialization για να δημιουργήσει μια κόπια του
current instance. Αυτό δουλεύει μόνο αν στην εκάστοτε κλάσση μαρκάρω ως
[Serializable] ...
[Serializable]
public
abstract class
BinaryCloneableObject
{
public object
Clone(){
// Serialize the object in memory ...
IFormatter
formater = new BinaryFormatter();
MemoryStream
memoryBuffer = new MemoryStream();
formater.Serialize(memoryBuffer,
this);
// 'rewind' the stream
memoryBuffer.Position
= 0;
// Deserialize into an object ...
return
formater.Deserialize(memoryBuffer);
}
}
.. και για να το τεστάρω ως απόδωση, έγραψα αυτό το προγραμματάκι το οποίο κάνει κλώνους απο 2 κλάσσεις (
και οι 2 έχουν απλώς ένα member string με τιμή "my_string") 1000 φορές, και δείχνει στην οθόνη το μέσο όρο σε ticks για κάθε κλώνο ...
static void
Main(string[] args)
{
long tst1Time =
0;
long tst2Time =
0;
for(int i=0; i<1000; i++){
Test1 tst1 = new
Test1();
Test2 tst2 = new
Test2();
long
startTime = DateTime.Now.Ticks;
Test1 _tst1 = (Test1)tst1.Clone();
long
endTime = DateTime.Now.Ticks;
tst1Time += endTime - startTime;
startTime = DateTime.Now.Ticks;
Test2 _tst2 = (Test2)tst2.Clone();
endTime = DateTime.Now.Ticks;
tst2Time += endTime - startTime;
}
Console.WriteLine("Test 1: {0}",
tst1Time/1000);
Console.WriteLine("Test 2: {0}",
tst2Time/1000);
Console.ReadLine();
}
... παραδόξως ... η μέθοδος χωρίς το binary serialization φαίνεται να
είναι πιο γρήγορη στην πλειοψηφία των test runs που έτρεξα (
5-10 έτρεξα, μην αγχωνόμαστε Δευτεριάτικα ...
) , και αν και κάποιες φορές ήταν πιο αργή, αυτή ήταν μια στις 5
περίπου ... τυπικά, η μέθοδος χωρίς το binary serialization ήταν απο
200 έως και 781 (!!!) ticks πιο γρήγορη απ'την άλλη ...
Δεν ξέρω γιατί συνέβη αυτό (
είναι όντως τόσο πιο βαρύ το serialization??), και γιατί ο ένας τρόπος είναι πιο γρήγορος απ'τον άλλο (
κι ακόμη δεν είμαι πεπεισμένος οτι είναι όντως πιο γρήγορος .. ) γι'αυτό και παραθέτω εδώ αυτό το post για να το .. φιλοσοφήσουμε το θέμα :)
Καλή μας μέρα
Angel
O:]