Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


My Not-So-Evil Twin

Ken explains a process for deep-cloning .NET objects.

n a recent instance of this column, I referred to my "evil twin," as in: "And I didn't write this article. My evil twin did it. Don't come looking for me." This week, the most surreal thing I've ever had happen occurred, and it's affected my view of the world.

I was performing my weekly "vanity search" on Google, looking for my name within the public newsgroups, just to see if anyone had any new complaints about something I had written, or said, in public. (Come on—you do this too, right? Every geek I know checks for references to themselves every now and then. I'm not going to feel embarrassed about how this all came about. At least, I'll convince myself that everyone does it.)

Instead of finding references, what I found were two beginner-level questions posted to the public newsgroups, by me! Well, not quite. The questions were posted by another developer named Ken Getz. I have to ask: What are the chances that there would be another developer in the Microsoft space, asking questions about ADO.NET (a topic I write and speak about often), with the same name? I'd say the chances are relatively small. Infinitesimal, at best. At worst, it happened. There are two of me. And the public can't tell which is which.

When asked on the newsgroups if he was the Ken Getz who had written all the books and articles, the "bizarro Ken" (that's a reference to many Superman comics, and of course, the famous Seinfeld episode with "bizarro Jerry", et al—not any kind of insult to the character of the other fella) replied "I don't know any 'Ken Getz' but me. And I did not write any books."

I know you're probably not terribly sympathetic—I mean, how can it be a problem that there's two folks with the same name posting in the same areas on the public newsgroups? The problem is that I make my living pretending that I know what the heck I'm talking about in public—people hand me money because they believe. Like many of you, I've been relatively successful at performing that sleight of hand for years. I think of the famous New Yorker cartoon, with the picture of the canine computer user, confiding to his equally canine friend: "On the Internet, no one can tell you're a dog." In this case, on the Internet, no one can tell which Ken Getz they're talking to. If you're my twin out there, reading this, I apologize in advance for any efforts I make to clear up who's posting publicly.

Speaking of cloning—yes, I know we weren't, but wouldn't it be fun if they actually could clone adult pets? I'd take a clone of my Persian kitty—this whole imposterization incident reminded me of one of my favorite tips. That is, how do you create a clone of a complex object? For example, imagine that you've got a class defined that contains references to other classes, so that an instance of the class might have several objects hanging off of it. Now imagine that you need to create a copy of the complete object graph (as this sort of thing is often called), so that you can work with the clone of the original.

If you start digging through the various methods of your object, you'll find that the base Object class provides a MemberwiseClone method. Commonly, developers follow the standard design patter for this, by implementing the ICloneable interface, and by creating the required Clone method.

' VB Public Class YourClass Implements ICloneable Public Function Clone() As Object _ Implements ICloneable.Clone Return Me.MemberwiseClone() End Function End Class // C# public class YourClass: ICloneable { public Object Clone() { return this.MemberwiseClone(); } }

This works fine. Once you've implemented ICloneable, and have added the Clone method, code that consumes this class can happily call the Clone method, expecting that it's receiving a clone of the original object, as well as all its subordinate objects. Unfortunately, that's not the case—the MemberwiseClone method is documented as returning a "shallow" copy. In English, this means that the copy you get is indeed a copy of the top-level object, but that's as far as it goes. The remaining references are still the same references as in the original object, so changes made to properties of the clone that are themselves objects are made to the original objects. There's only one of each of the sub-objects, and that's not what you'd expect.

The solution is amazingly simple. I can't really take credit for this idea, however. When I was tech-editing an early Visual Basic book for Gary Cornell, I came across this technique in Gary's coverage of .NET serialization. Gary's solution for creating a deep clone? You just need to serialize the top-level object into a stream, rewind the stream, and deserialize it into a new object. It really couldn't be easier.

The following code snippet demonstrates one solution to this common problem, using Gary's suggestion.

' VB Shared Function DeepClone( _ ByVal obj As Object) As Object ' Create a "deep" clone of ' an object. That is, copy not only ' the object and its pointers ' to other objects, but create ' copies of all the subsidiary ' objects as well. This code even ' handles recursive relationships. Dim ms As New MemoryStream Dim objResult As Object = Nothing Try Dim bf As New BinaryFormatter bf.Serialize(ms, obj) ' Rewind back to the beginning ' of the memory stream. ' Deserialize the data, then ' close the memory stream. ms.Position = 0 objResult = bf.Deserialize(ms) Finally ms.Close() End Try Return objResult End Function // C# public static object DeepClone(object obj) { // Create a "deep" clone of // an object. That is, copy not only // the object and its pointers // to other objects, but create // copies of all the subsidiary // objects as well. This code even // handles recursive relationships. object objResult = null; using (MemoryStream ms = new MemoryStream()) { BinaryFormatter bf = new BinaryFormatter(); bf.Serialize(ms, obj); // Rewind back to the beginning // of the memory stream. // Deserialize the data, then // close the memory stream. ms.Position = 0; objResult = bf.Deserialize(ms); } return objResult; }

If you're using Visual Basic 2005, of course, I'd suggest that you use the same Using construct as demonstrated in the C# code—it's simpler, and cleaner. If you're using Visual Basic 2003, you must use the Finally block to close the stream when you're done with it. Note that the code requires two Imports/using statements:

' VB Imports System.IO Imports System.Runtime.Serialization.Formatters.Binary // C# using System.IO; using System.Runtime.Serialization.Formatters.Binary;

Give it a try-create an instance of a complex object and call the DeepClone method, passing in your object. The return value will be a complete copy of the original object, with all the properties cloned, as well. (Imagine the code you'd have to write to solve this problem generally. You could, of course, use a lot of .NET Reflection to determine all the various properties and their types, navigate through the new object, and populate it with data. Or even worse, not solve it generally, and write specific code for each complex object. Ugh.)

The DeepClone method, or at least, the technique behind it, can solve many issues involving copies of complex objects. In the case of this particular complex object, I don't have an easy solution. I'll have to deal with the fact that there's multiples of "me" out there, sharing the same public identity. If you see me or anyone else you think you know online, posting a question which makes it look like we've forgotten something we surely should know, remember—it might not actually be who you think it is. That DeepClone method is extremely powerful, I guess.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date