In the “Understanding Reference Types” section, earlier in this chapter, you saw how reference type assignments differ from value type assignments and how assignments are not enough to create a copy of a reference type. You also saw one basic solution to this problem, which involved creating a new instance of a specified reference type and then assigning each property of the target instance with values coming from the original one. But this is not enough, both because it is not complete and because it can be good only with small classes.
To create a complete clone of a reference type, in.NET development, you can take advantage of two techniques: deep copy and shallow copy. Both of these techniques require implementation of the ICloneable
interface. Although we discuss interfaces later, the concepts presented here are easy to understand. The ICloneable
interface provides a unique method named Clone
that enables you to know what classes can be easily cloned. For example, consider the following implementation of the Person
class that also implements the ICloneable
interface:
Class Person
Implements ICloneable
Property FirstName As String
Property LastName As String
Property Work As Job
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
The most interesting thing in the code is the Clone
method required by the ICloneable
interface. In the method body, you write code that performs the real copy of the reference type. Fortunately, the Object
class provides a method named MemberwiseClone
that automatically returns a shallow copy of your reference type. The keyword Me
indicates the current instance of the class. Because Clone
must work with all possible types, it returns Object
. (You’ll see later in this chapter how to convert this result.) The class exposes two String
properties, FirstName
and LastName
. You might remember that although String
is a reference type behind the scenes, you actually treat it as a value type. The class also exposes another property, named Work
, of type Job
. This is a new reference type representing a person’s occupation. Job
is implemented as follows:
Class Job
Property CompanyName As String
Property Position As String
End Class
Given this implementation, you can simply create a shallow copy.
When you create a shallow copy, you create a new instance of the current object and copy values of members of the original to the new one but do not create copies of children (referenced) objects.
Let’s continue the example from the preceding section. Clone
creates a copy of the Person
class into a new instance and copies members’ values that are value types or Strings
. Because Job
is a pure reference type, the shallow copy provided by Clone
will not also create a clone of Job
. (Remember that a shallow copy creates a copy only of the specified instance but not of children objects.) You can easily verify these assertions by writing the following code:
Sub Main()
'The original person
Dim firstPerson As New Person
firstPerson.FirstName = "Alessandro"
firstPerson.LastName = "Del Sole"
'Defines a work for the above person
Dim randomJob As New Job
randomJob.CompanyName = "Del Sole Ltd."
randomJob.Position = "CEO"
'Assignment of the new job
firstPerson.Work = randomJob
'Gets a shallow copy of the firstPerson object
Dim secondPerson As Person = CType(firstPerson.Clone, Person)
'Check if they are the same instances
'returns False, 2 different instances:
Console.WriteLine(firstPerson.FirstName Is secondPerson.FirstName)
'returns True (still same instance of Job!):
Console.WriteLine(firstPerson.Work Is secondPerson.Work)
Console.ReadLine()
End Sub
This code first gets a new instance of the Person
class and sets some properties such as a new instance of the Job
class, too. Notice how the result of the Clone
method, which is of type Object
, is converted into a Person
instance, using CType
. At this point, you can check what happened. The Is
operator enables you to compare two instances of reference types and returns True
if they are related to the same instance. For the FirstName
property, the comparison returns False
because the shallow copy creates a new, standalone instance of the Person
class. But if you do the same check on the Work
property, which is a child reference type of the Person
class, the comparison returns True
. This means that firstPerson.Work
refers to the same instance of the Job
class as in secondPerson.Work
. And this also means that a shallow copy did not create a new copy of the Job
class to be assigned to the secondPerson
object. This is where the deep copy comes in.
Creating a deep copy is a complex way to create a perfect copy of an entire object’s graph. If you need to perform a deep copy, you have some alternatives. The easiest way is to perform a shallow copy of the main object and then manually copy the other properties of child reference types. Later in this chapter, you’ll instead learn about an alternative technique related to serialization, which is discussed in Chapter 39, “Serialization.” At the moment you can focus on editing the previous implementation of the Clone
method for performing a simple deep copy. You can also implement the ICloneable
interface in the Job
class, as follows:
Class Job
Implements ICloneable
Property CompanyName As String
Property Position As String
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
Now you can modify the Clone
implementation inside the Person
class, as follows:
Class Person
Implements ICloneable
Property FirstName As String
Property LastName As String
Property Work As Job
Public Function Clone() As Object Implements System.ICloneable.Clone
Dim tempPerson As Person = CType(Me.MemberwiseClone, Person)
tempPerson.Work = CType(Me.Work.Clone, Job)
Return tempPerson
End Function
End Class
Implementing ICloneable
Implementing ICloneable
in referenced classes is not the only way of providing deep copies. You could also generate a new instance of the Job
class and manually assign values read from the original instance. But because we are discussing ICloneable
and Clone
, the preceding example is completed this way.
The code first obtains a shallow copy of the current instance of the Person
class and then gets a shallow copy of the child instance of the Job
class.
Recursive Cloning
Cloning objects with recursive calls to the Clone
method could lead to a stack overflow if the hierarchy of objects is particularly complex. Because of this, the previous implementation goes well with small classes and small object graphs. In other situations, you should instead use serialization.
If you now try to run the same check again, comparing the instances of firstPerson
and secondPerson
, the output will be as follows:
False
False
This is because now the instances are different. You have two completely standalone instances of the Person
class.
3.133.82.244