In .NET, class objects are reference types. Assigning one object variable to another object variable does not copy that object, it simply causes both object variables to reference the same object.
Sometimes, a copy is required. For example, maybe two routines need to start with the same data but then change that data independently from each other. Copying the data ensures that changes made by one routine will not impact the data being used by the other routine.
When using .NET, two types of copies are possible: shallow and deep. In the case of a shallow copy, a new object is created and each member from the original object is assigned to the corresponding member of the new object. In the case of value members, this is a copy in the truest sense. However, with objects that contain reference members, this does not produce a true copy.
One example of a reference type is a string
. When you assign one string
variable to another, both variables will reference the same string
data. The characters of the string
s are not truly copied. So if a class contains reference members, a shallow copy does not create a true copy of all class members.
For many cases, a shallow copy is sufficient. Note that string
s are immutable and cannot be changed. When you create a shallow copy of an object that contains string
s, and then modify a string
in the new object, that would create a new string
and would not have any impact on the original string
in the original object. Note that other data types such as arrays, class objects, and arrays of class objects can be quite a bit more complicated than string
s.
A deep copy is when a copy is created that contains none of the original data. A true copy of each member is created. A deep copy doesn’t need to do anything special with members that are value types. But for reference data types, the new object must reference copies of that data instead of the original data.
There is nothing unique about how either method of copying an object are performed. Consider listing 1. This code declares a class called MyClass
, and then shows a short method called Test
that performs both a shallow and a deep copy using that class
object.
protected class MyClass
{
public int i;
public int j;
public string message;
}
private void Test()
{
MyClass mc1;
MyClass mc2;
mc1 = new MyClass();
mc1.i = 5;
mc1.j = 10;
mc1.message = "Hello, World!";
mc2 = new MyClass();
mc2.i = mc1.i;
mc2.j = mc1.j;
mc2.message = mc1.message;
mc2 = new MyClass();
mc2.i = mc1.i;
mc2.j = mc1.j;
mc2.message = String.Copy(mc1.message);
}
Listing 1: Shallow and deep copying of an object.
The shallow copy does nothing special. It simply assigns each member from one object to the other. For value members, the deep copy uses the same code. However, for the one reference member, message, the code must create a copy of the string
data. (Note that addition steps would be required to perform a deep copy with objects that include reference members with references to additional objects, such as class members, arrays, etc.)
Now that I’ve hopefully explained the difference between a shallow and a deep copy, let’s take a look at some of the tools the .NET Frameworks provide to perform these tasks.
protected class MyClass : ICloneable
{
public int i;
public int j;
public string message;
public object Clone()
{
return MemberwiseClone();
}
}
private void Test()
{
MyClass mc1 = new MyClass();
mc1.i = 5;
mc1.j = 10;
mc1.message = "Hello, World!";
MyClass mc2 = (MyClass)mc1.Clone();
}
Listing 2: Using MemberwiseClone() to perform a shallow copy.
Listing 2 uses MemberwiseClone()
to perform a shallow copy. MemberwiseClone()
is protected
and so cannot be called directly from Test
. Instead, I’ve modified MyClass
to implement the ICloneable interface
and implemented the one ICloneable
method, Clone
. (Normally, ICloneable
is associated with a deep copy but I use it here to implement a shallow copy.) The Test
method calls this new method to perform the shallow copy. Since Clone()
returns type
object, a type cast is required.
To perform a deep copy, Listing 3 also implements the ICloneable
interface. This listing just modifies the code in the Clone()
method to perform a deep copy.
protected class MyClass : ICloneable
{
public int i;
public int j;
public string message;
public object Clone()
{
MyClass mc = new MyClass();
mc.i = i;
mc.j = j;
if (message != null)
mc.message = String.Copy(message);
return mc;
}
}
private void Test()
{
MyClass mc1 = new MyClass();
mc1.i = 5;
mc1.j = 10;
mc1.message = "Hello, World!";
MyClass mc2 = (MyClass)mc1.Clone();
}
Listing 3: Using ICloneable to perform a deep copy.
The actual code in the Clone()
method should be familiar by now. The main advantage to implementing it this way is that it is implemented as part of the class, where it can easily be modified and called from anywhere in your application.
Nothing too complex here, although the concept behind a shallow and deep copy can be confusing to some. Hopefully, I’ve shown some light on this topic and demonstrated how you might approach the issue using .NET.