Larkware
We get up early so that you don't have to.

Cloning in VB .NET

By Mike Gunderloy
Thursday, April 17, 2003

I had someone write to me yesterday asking for an example of using ICloneable.Clone in a Visual Basic .NET class. Well, here's a quick example, for anyone else who hasn't dug into this corner of the .NET world. I'll start with a simple class hierarchy, in which one recipe can contain many ingredients:

Public Class Recipe

    Public Name As String
    Public Ingredients As IngredientCollection

    Public Sub New()
        Ingredients = New IngredientCollection
    End Sub

End Class

Public Class Ingredient
    Public Name As String
End Class

(I'm leaving out the boring details of implementing the strongly-typed collection; I used CodeSmart to do it for me anyhow).

Now, let's add a method to the Recipe class that uses the built-in Object.MemberwiseClone method:

Public Function ShallowCopy() As Recipe
    Return Me.MemberwiseClone
End Function

With that in place, I can create a form to make what's called a "shallow copy" of an object. Here's some code to demonstrate:

Private Sub btnShallow_Click(ByVal sender As System.Object_
 ByVal e As System.EventArgsHandles btnShallow.Click
    ' Clear the listbox
    lbRecipes.Items.Clear()

    ' Create a new recipe
    Dim R1 As Recipe = New Recipe
    R1.Name = "Ham Sandwich"
    Dim I1 As Ingredient = New Ingredient
    I1.Name = "Ham"
    Dim I2 As Ingredient = New Ingredient
    I2.Name = "Bread"
    R1.Ingredients.Add(I1)
    R1.Ingredients.Add(I2)

    ' Make a shallow copy
    Dim R2 As Recipe = R1.ShallowCopy()
    R2.Name = "Cheese Sandwich"
    R2.Ingredients(0).Name = "Cheese"

    ' Show the results
    DumpRecipe(R1)
    DumpRecipe(R2)
End Sub

And here are the results:

[Shallow copy screenshot]
Note the problem here. Although the new Recipe object has its own properties (so its name can be set independently), it shares pointers to subsidiary objects with the original Recipe object. Thus, changing an ingredient name in the second recipe changes the corresponding ingredient name in the original recipe. This is probably not what you want when you make a copy of an object.

Enter ICloneable. The purpose of the ICloneable interface is to provide a single method, Clone, which makes a "deep copy" of an object: one which has all the properties of the original object, but which is completely disconnected from it. Of course, like any other interface, you need to implement the details yourself. Here's a revised Recipe class implementing ICloneable:

Public Class Recipe
    Implements ICloneable

    Public Name As String
    Public Ingredients As IngredientCollection

    Public Sub New()
        Ingredients = New IngredientCollection
    End Sub

    Public Function ShallowCopy() As Recipe
        Return Me.MemberwiseClone
    End Function

    Public Function Clone() As Object Implements System.ICloneable.Clone
        ' Start with a shallow copy
        Dim R As Recipe = Me.MemberwiseClone
        ' But now give it a new Ingredients collection
        R.Ingredients = New IngredientCollection
        ' And populate it from the original
        Dim INew As Ingredient
        For Each I As Ingredient In Me.Ingredients
            INew = New Ingredient
            INew.Name = I.Name
            R.Ingredients.Add(INew)
        Next
        Return R
    End Function

End Class

Note that I'm using the new VB .NET 2003 syntax on the For Each loop; if you're using 2002, you'll need to define the loop variable before the loop. Here's the test code:

Private Sub btnDeep_Click(ByVal sender As System.Object_
 ByVal e As System.EventArgsHandles btnDeep.Click
    ' Clear the listbox
    lbRecipes.Items.Clear()

    ' Create a new recipe
    Dim R1 As Recipe = New Recipe
    R1.Name = "Ham Sandwich"
    Dim I1 As Ingredient = New Ingredient
    I1.Name = "Ham"
    Dim I2 As Ingredient = New Ingredient
    I2.Name = "Bread"
    R1.Ingredients.Add(I1)
    R1.Ingredients.Add(I2)

    ' Make a deep copy
    Dim R2 As Recipe = R1.Clone()
    R2.Name = "Cheese Sandwich"
    R2.Ingredients(0).Name = "Cheese"

    ' Show the results
    DumpRecipe(R1)
    DumpRecipe(R2)
End Sub

And here are the results:

[Deep Copy screenshot]
All better! Here's the VS .NET 2003 project if you'd like to avoid cutting and pasting the code.

Generated using PrettyCode.Encoder

Mike Gunderloy is the lead developer for Larkware and author of numerous books and articles on programming topics.

For a deeper look at some of the issues involved in copying and cloning, along with sample code in C#, take a look at Shawn Van Ness's article Copying, Cloning, and Marshalling in .NET on ONDotnet.

Home