Click here to monitor SSC

Robert Chipperfield

Cloning objects the quick and dirty way

Published Friday, September 25, 2009 4:09 PM

Working in .NET, a lot of the objects I use implement ISerializable - very handy when it comes to throwing them around using .NET Remoting and the like. In my slightly more evil (or is it lazy?) moments, I tend to "re-purpose" this for a lightweight clone mechanism...

It's quite common when writing test code to be able to do a deep copy of an object - maybe to keep a copy of its state before performing an operation to then compare it again later, or maybe to "subclass" an existing object, overriding a given property with something else. Even preventing certain properties going over the wire or to disk because they aren't required, all without the original object knowing anything of the devious things going on.

The problem is, there's no generic deep copy facility available in .NET (correctly so), and I generally don't want to have to implement IClonable on everything as well. What to do?

Assume I have a couple of little classes, Outer, and Inner, where Outer has a property of type Inner:

    [Serializable]
    class Outer : ISerializable
    {
        public Inner Inner { get; set; }

        public Outer() { }

        public Outer(SerializationInfo info, StreamingContext context)
        {
            Inner = (Inner)info.GetValue("Inner", typeof(Inner));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Inner", Inner);
        }
    }

    [Serializable]
    class Inner : ISerializable
    {
        public Inner() { }

        public Inner(SerializationInfo info, StreamingContext context) { }

        public void GetObjectData(SerializationInfo info, StreamingContext context) { }
    }

My first solution was along these lines - very compact, and seemed to work:

    class ClonedOuter : Outer
    {
        public ClonedOuter(Outer o)
            : base(GetSerializationInfo(o), new StreamingContext ())
        { }

        private static SerializationInfo GetSerializationInfo(Outer o)
        {
            SerializationInfo info = new SerializationInfo(typeof(Outer), new FormatterConverter());
            o.GetObjectData(info, new StreamingContext());
            return info;
        }
    }

The theory went that Outer would serialize its state to the SerializationInfo I provided in GetSerializationInfo, and that would then get passed to the deserialization constructor of Outer via the base(...) call, giving a nicely duplicated object. And it did! But then I hit a problem:

            Outer clone1 = new ClonedOuter(original);
            Console.WriteLine("clone1.Inner == original.Inner: " + (clone1.Inner == original.Inner)); // Prints true!

Whilst the Outer object was being cloned, the Inner one was not - it was simply the same object. The reason for this is hinted at in the documentation: "Objects are reconstructed from the inside out." The GetObjectData of Inner wasn't even being called, let alone being used.

Back to the drawing board. This time, a slightly more "authorized" use of serialization:

        public static Outer Clone(Outer o)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                bf.Serialize(ms, o);
                ms.Seek(0, SeekOrigin.Begin);
                return (Outer)bf.Deserialize(ms);
            }
        }

Here we actually force the object graph to be serialized to a MemoryStream (basically a wrapper around a byte[]), and the results are as expected. Much better. But it's not perfect: I quite like the ability to have a "copy constructor" of the form "new ObjectyThing(ObjectyThing source)", and the static Clone method doesn't give me the opportunity to subclass the cloned copy.

So then you combine both approaches:

    class ClonedOuter2 : Outer
    {
        public ClonedOuter2(Outer o)
            : base(GetSerializationInfo(Clone(o)), new StreamingContext())
        { }

        public static Outer Clone(Outer o)
        {
            BinaryFormatter bf = new BinaryFormatter();
            using (MemoryStream ms = new MemoryStream())
            {
                bf.Serialize(ms, o);
                ms.Seek(0, SeekOrigin.Begin);
                return (Outer)bf.Deserialize(ms);
            }
        }

        private static SerializationInfo GetSerializationInfo(Outer o)
        {
            SerializationInfo info = new SerializationInfo(typeof(Outer), new FormatterConverter());
            o.GetObjectData(info, new StreamingContext());
            return info;
        }
    }

I dread to think of the efficiency of this... :-)

Comments

No Comments
You need to sign in to comment on this blog

About RobertChipperfield

I'm a software engineer at Red Gate, where I've worked since September 2006. I've worked on a wide range of products, including Exchange Server Archiver, SQL Data Compare, SQL Log Rescue, SQL Multi Script, SQL Doc, and ANTS Profiler.

Outside of work, I enjoy amateur radio, electronics, and of course the usual assortment of computer-related technologies, from hardware all the way through to high-level software.

<September 2009>
SuMoTuWeThFrSa
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910
How to Kill a Company in One Step or Save it in Three
 The majority of companies that suffer a major data loss subsequently go out of business. Wesley David... Read more...

Migrating from OCS 2007 R2 to Lync: Part 4
 Having migrated the rest of our users and legacy resources across and started getting ready to... Read more...

Automated Script-generation with Powershell and SMO
 In the first of a series of articles on automating the process of building, modifying and copying SQL... Read more...

Seth Godin: Big in the IT Business
 Seth Godin has transformed our understanding of marketing in IT. He invented the concept of 'permission... Read more...

Using SQL Test Database Unit Testing with TeamCity Continuous Integration
 With database applications, the process of test and integration can be frustratingly slow because so... Read more...