May 102010
 

Recently I was killing time on stackoverflow and found this question:

What is the best comment in source code you have ever encountered?

There are a lot of great quotes in there. If you haven’t seen it already it worth a read when you want to kill some time. I’d also add my own to the list:

    // The DM warns the following encounter is EL25 and should only be attempted
    // by Epic level parties.
 

As promised in the previous post “How to implement a generic Shallow-Clone for an object in C#” this will demonstrate deep-cloning objects that don’t explicitly support the behavior.

Before I continue I will again state that I don’t recommend this approach. It will be many times slower than a properly implemented Clone() and can cause rather strange side-effects. For instance, deep-cloning a tree of objects that hold reference to a singleton service class would result in cloning that singleton. This problem simply stems from the fact that no generic code can have enough knowledge about the internal structure of classes to be able to accurately guess when and when not to clone a member reference. Thus if you going to code the logic of which members to clone or not, it’s best done in the objects themselves and is the primary reason I say a generic deep clone is a bad idea. Even if you don’t currently have a reference to a singleton now, what happens when someone adds one to the object graph? How will your application behave?

So with this established as a bad idea it is with great trepidation that I demonstrate how to achieve this. To start with let’s demonstrate usage in the Library:

using CSharpTest.Net.Cloning;

    //Uses serialization API if available or member-wise if non-serializable
    using (ObjectCloner cloner = new SerializerClone())
        fooCopy = cloner.Clone(foo);
    // 3.5 can use extension method instead of the code above:
    fooCopy = foo.DeepClone();

    //To ignore serialization routines, use the following:
    using (ObjectCloner cloner = new MemberwiseClone())
        fooCopy = cloner.Clone(foo);

Implementation details, there are not a lot to share, you can see the source easily enough at:
http://csharptest.net/browse/src/Library/Cloning

There are a few things worth pointing out. The ObjectCloner provides a base-class that handles the object graph and a few well-known types (delegates, arrays, primitives, etc). Derived from this is the MemberwiseClone class which provides a basic ‘memory copy’ type of copy operation. Again deriving from this is the SerializerClone which implements a pseudo serialization routine by attempting to mimic the .Net serializer’s default implementation. This results in three possible types of copy operations:

1. If ISerializable is implemented it will be used, allowing customization of the copy.
2. If [Serializable] decoration is present the FormatterServices.GetSerializableMembers is used to allow respecting the [NonSerialized] attribute.
3. Lastly if the object doesn’t support serialization a straight member-by-member copy is made.

This continues throughout the object graph until all object members have been copied into the new tree. Finally if appropriate calls to IDeserializationCallback will be made just prior to the top-level instance being returned.

One thing of note is that the object graph is made public to allow preventing some instances from being copied. For instance if you want to copy foo’s complete object graph except for any instances of ‘bar’ then you would do this:

using CSharpTest.Net.Cloning;

    using (ObjectCloner cloner = new SerializerClone())
    {
        // maps instances of bar to itself:
        cloner.Graph.Add(bar, bar);
        fooCopy = cloner.Clone(foo);
    }