Reputation: 61636
I am trying to optimize a piece of code that clones an object:
#region ICloneable
public object Clone()
{
MemoryStream buffer = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(buffer, this); // takes 3.2 seconds
buffer.Position = 0;
return formatter.Deserialize(buffer); // takes 2.1 seconds
}
#endregion
Pretty standard stuff. The problem is that the object is pretty beefy and it takes 5.4 seconds (according ANTS Profiler - I am sure there is the profiler overhead, but still).
Is there a better and faster way to clone?
Upvotes: 8
Views: 6397
Reputation: 2271
Answer: There ARE better methods for cloning.
Reflection or Expression Trees are much faster then Serialization (reflection is 5x faster, expression trees are 20x faster).
If you use this linked cloning function as an extension method, each your cloning code shrinks to
#region ICloneable
public object Clone()
{
return this.DeepCopyByExpressionTree();
}
#endregion
To use the extension method it is enough to have the file DeepCopyByExptressionTrees.cs anywhere in your solution.
Upvotes: 4
Reputation: 2398
Because manually copying fields is the fastest way I created a code generator, that reads your class definition and generates a clone method. All you need is the CGbR nuget package and a partial class that implements ICloneable
. The generator will do the rest.
public partial class Root : ICloneable
{
public Root(int number)
{
_number = number;
}
private int _number;
public Partial[] Partials { get; set; }
public IList<ulong> Numbers { get; set; }
public object Clone()
{
return Clone(true);
}
private Root()
{
}
}
public partial class Root
{
public Root Clone(bool deep)
{
var copy = new Root();
// All value types can be simply copied
copy._number = _number;
if (deep)
{
// In a deep clone the references are cloned
var tempPartials = new Partial[Partials.Length];
for (var i = 0; i < Partials.Length; i++)
{
var value = Partials[i];
value = value.Clone(true);
tempPartials[i] = value;
}
copy.Partials = tempPartials;
var tempNumbers = new List<ulong>(Numbers.Count);
for (var i = 0; i < Numbers.Count; i++)
{
var value = Numbers[i];
tempNumbers[i] = value;
}
copy.Numbers = tempNumbers;
}
else
{
// In a shallow clone only references are copied
copy.Partials = Partials;
copy.Numbers = Numbers;
}
return copy;
}
}
And the partial class
public partial class Partial : ICloneable
{
public short Id { get; set; }
public string Name { get; set; }
public object Clone()
{
return Clone(true);
}
}
public partial class Partial
{
public Partial Clone(bool deep)
{
var copy = new Partial();
// All value types can be simply copied
copy.Id = Id;
copy.Name = Name;
return copy;
}
}
Upvotes: 1
Reputation: 76550
That's a pretty expensive way to clone. The object never gets on the wire, so all the time doing serialisation is basically wasted. It will be way faster to do memberwise clone. I realise it's not an automagic solution, but it'll be the fastest.
Something along these lines:
class SuperDuperClassWithLotsAndLotsOfProperties {
object Clone() {
return new SuperDuperClassWithLotsAndLotsOfProperties {
Property1 = Property1,
Property2 = Property2,
}
public string Property1 {get;set;}
public string Property2 {get;set;}
}
}
Upvotes: 1
Reputation: 3207
As I understand it, streams, even inner ones like this, are expensive.
Have you tried to just create a new object it and update the relevant fields to bring the object to the same state? I find it hard to believe your method takes less time.
Upvotes: 1
Reputation: 217351
Don't implement ICloneable.
The fast way to clone an object is to create a new instance of the same type and copy/clone all fields from the original instance to the new instance. Don't try to come up with a "generic" clone method that can clone any object of any class.
Example:
class Person
{
private string firstname;
private string lastname;
private int age;
public Person(string firstname, string lastname, int age)
{
this.firstname = firstname;
this.lastname = lastname;
this.age = age;
}
public Person Clone()
{
return new Person(this.firstname, this.lastname, this.age);
}
}
Upvotes: 10