8

First of all we probably all agree that the best way would be to implement Copy function inside custom object/entity. But consider this scenario. We don't have this option and we don't want to write specific function that will make exact copy of entity because entity will be changed in the future so our copy function would fail.

Here is simplified version of current entity:

[Serializable]
class MyEntity
{
    public MyEntity()
    { 
    }

    public MyEntity(int id, string name)
    {
        this.Id = id;
        this.Name = name; 
    }

    public int Id { get; set; }

    public string Name { get; set; }

    public MyEntity Copy()
    {
        throw new NotImplementedException();
    }
}

To cover all the requirements above I came up with two solutions:

        //original...
        MyEntity original = new MyEntity() { Id = 1, Name = "demo1" };

        //first way to copy object...
        List<MyEntity> list = new List<MyEntity>() { original};
        MyEntity copy1 = list.ConvertAll(entity => new MyEntity(entity.Id, entity.Name))[0];

        //second way to copy object...
        byte[] bytes = SerializeEntity(original);
        MyEntity copy2 = (MyEntity)DeserializeData(bytes);


    byte[] SerializeEntity(object data)
    {
        byte[] result = null;
        using (MemoryStream ms = new MemoryStream())
        {
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(ms, data);
            result = ms.ToArray();
        }
        return result;
    }

    object DeserializeData(byte[] data)
    {
        object result = null;
        using(MemoryStream ms = new MemoryStream(data))
        {
           BinaryFormatter formatter = new BinaryFormatter();
           result = formatter.Deserialize(ms); 
        }
        return result;
    }

And now the question. What solution is the most optimal behind the scene and why, first or second? Is there any better way to do exact copy considering requirements above? Copies will be done in large numbers.

PS note: I am aware that first way is basicly already a copy function as Honza pointed out. I am kind of looking something versatile as serialization and near fast as custom copy function.

Gregor Primar
  • 6,759
  • 2
  • 33
  • 46

3 Answers3

7

First of all we probably all agree that the best way would be to implement Copy function inside custom object/entity.

I don't agree. I hate to write such methods every time. Here is my suggestion using an extension method:

public static T Copy<T>(this T obj)
    where T : class
{
    using (MemoryStream stream = new MemoryStream())
    {
        BinaryFormatter formatter = new BinaryFormatter();
        formatter.Serialize(stream, obj);

        stream.Seek(0, SeekOrigin.Begin);
        return formatter.Deserialize(stream) as T;
    }
}

This is basically your second solution but slightly optimized. There is no need to copy the MemoryStream to a byte array and then create another MemoryStream from that.

The best thing is that it is generic an can be used with every object that has the [Serializable] attribute. And i'm quite sure that it is faster than your first solution where you have to access each property (although I didn't measure).

Edit:

Ok, I actually did some measuring now. My first assumption for the performance was completely wrong!

I created 1000000 MyEntity objects with random values and then copied them (I also considered Honza Brestan's hint on deep and shallow copies):

deep copy with Binary formatter: 14.727 s
deep copy with Copy method: 0.490 s
shallow copy with reflection: 5.499 s
shallow copy with Copy method: 0.144 s

pescolino
  • 3,086
  • 2
  • 14
  • 24
  • This is similar to something we use, however, it might be worth noting that the every time a different 'T' is supplied a new method will have to be generated by the compiler. This will cause a slight CPU overhead and more memory to be used. – MadSkunk Dec 23 '12 at 01:25
  • @pescolino using serialization is indeed the most bullet proof if object is marked as serializable, but it's also the slowest. I am realy hopping to get some smart and fast way to do a copy, however I am aware there is no "free lunch". You always loose something to gain other thing. – Gregor Primar Dec 23 '12 at 10:13
  • @GregorPrimar Read [Improving performance reflection, what alternatives should I consider](http://stackoverflow.com/questions/1027980/improving-performance-reflection-what-alternatives-should-i-consider) and especially [Jon Skeet's blog](http://msmvps.com/blogs/jon_skeet/archive/2008/08/09/making-reflection-fly-and-exploring-delegates.aspx) as mentioned in his answer there. – pescolino Dec 24 '12 at 16:45
1

What is the difference between your first attempt and writing your own Copy method?

public MyEntity Copy()
{
    return new MyEntity(this.Id, this.Name);
}

For me this looks better than your collection attempt, which does exactly the same anyway - in both cases you have to explicitly name all the properties.

If you cannot modify the entity class itself, you can still create an extension method (placed in a static class visible from where you want to use the copy logic)

public static MyEntity Copy(this MyEntity source)
{
    return new MyEntity(source.Id, source.Name);
}

As for the second attempt, have you considered the differences between the two? They're not quite the same. First one creates a shallow copy, while the second one (provided the whole object tree is serializable) produces a deep copy. The difference is whether its properties are copied as well or both original object and its copy reference the same objects. The same applies to pescolino's version, which looks very nice BTW.

So the question is which copy do you want/need.

For a truly dynamic (yet probably not quite effective) shallow copy method I think you will need to use reflection, enumerating all the properties and copying their values from the original object to the copy. Non-complete demo version might look like this:

public static MyEntity Copy(this MyEntity source)
{
    var result = new MyEntity();

    var properties = source.GetType().GetProperties(
          BindingFlags.Instance | BindingFlags.Public);

    foreach (var property in properties)
    {
        var val = property.GetValue(source, null);
        property.SetValue(result, val, null);
    }

    return result;
}

This approach has problems of its own, namely performance, occasional need to handle special cases (indexers, non-public properties...), but will get the job done and works also on unserializable objects. Generic version would also be easy to do - that's up to you, whether you need it.

Also worth noting because both me and pescolino proposed using extension methods, there's a possible problem with them. If your entity really contains a Copy method with the same signature as the extension, the compiler will decide to use it instead of the extension. That will obviously throw the NotImplementedException when called. So if this is the case (and it's not just your sample code), it could have been a serious "gotcha". The only solution in such case is to change the extension method's signature, preferably by changing its name.

Honza Brestan
  • 10,637
  • 2
  • 32
  • 43
  • I agree with you it's not a big difference. The only "bright" point is that I will get exception if constructor is changed. In the first place I was looking to write the fastest possible option, create new instance and set individual properties. However in this approach I would not get an exception if new property is introduced. Basicly making invalid copy without knowing about it. Your sample of course covers this issue. – Gregor Primar Dec 23 '12 at 10:19
1

You could try to use AutoMapper:

Mapper.CreateMap<MyEntity, MyEntity>();

...

var copy3 = Mapper.Map<MyEntity, MyEntity>(original);
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • I had read few posts about AutoMapper but could not make any real conclusion about it's performance I can expect in my case. Some people claim that is not the best solution for all cases. Could you suggest any pointers that can result in bad performace using AutoMapper? +1 for alternative at this moment – Gregor Primar Dec 23 '12 at 10:22