ICloneable - Revisited
[Update: I copied the timing results from a test run that didn't do deep cloning for the LCG optimized cloning algorithm. This made it appear that LCG was faster than hand written code - which of course is ludicrous. I kind of wonder why no one noticed this.]
Jonas Follesø has a blog post on the performance differences between handwritten IClonable and reflection based ICloneable implementations. The example he uses is from a project I'm quite familiar with so I just had to blog about this.
The project he referes to uses serialization based cloning to a great extent. This is far from a performant way to clone objects, but its a very convenient way to do it. When developers don't have to write or maintain cloning code there is less work, less code and less chance of a bug being introduced when someone forgets to update the Clone method after adding a field.
Alternatives to the super easy serialization trick are reflection based solutions. Jonas referes to a naïve generic fieldwise deep clone algorithm in his post and concludes that the performance of hand-written code is superior to reflection. This shouldn't be a surprise to anyone - reflection has its costs.
public object Clone()
{
ImplementedCloneOrder[] clonedOrders=new ImplementedCloneOrder[Orders.Length];
for (int i=0;i<Orders.Length;i++)
{
clonedOrders[i] = (ImplementedCloneOrder) ((ImplementedCloneOrder)Orders[i]).Clone();
}
return
new ImplementedCloneCustomer(CustomerID, CompanyName, Address, City, Region, PostalCode, Country, Phone, Fax,
clonedOrders);
}
Another way to do reflection is to use bytecode optimized reflection. NHibernate does this to crank up the speed of its reflection operations. On .NET 1.x you can use the CodeDom to do this, while you're better of using LCG in .NET 2.0. So how does this compare to the other techniques? Below you can see the performance stats from cloning 100000 simple object graphs using different means.
Implemented Clone() method: Time: 128 ms, 458250 ticks (100000 operations)
Naïve reflection Clone() method: Time: 12566 ms, 44983213 ticks (100000 operations)
Optmized reflection Clone() method: Time: 866 ms, 239321 ticks (100000 operations)
Serialized Clone() method: Time: 25541 ms, 91425385 ticks (100000 operations)
As you can see the hardcoded Clone method is the fastest at only 128 ms, while optimized reflection takes a total of 866 ms to finish which still is rather fast.
With optimized reflection you can have the best of both worlds; great performance and easy to maintain code.
I'm in a bit of an hurry now, so you'll have to wait until tomorrow for the full code. Stay tuned!