- Make Your C# Classes Work with the .NET Framework
- Copy Semantics and ICloneable
- Comparing Objects
- Summary
Copy Semantics and ICloneable
Many times in programming, you have occasion to make a copy of a variable. When you program in C#, it is very important that you have a firm understanding of exactly what happens when you copy various kinds of data. In this section, we will look carefully at the copy semantics of C#. We will compare reference copy, shallow memberwise copy, and deep copy. You will see that by implementing the ICloneable interface in your class, you can enable deep copy.
Reference Copy
Recall that C# has value types and reference types. A value type contains all its own data, whereas a reference type refers to data stored somewhere else. If a reference variable gets copied to another reference variable, both will refer to the same object. If the object referenced by the second variable is changed, the first variable will also reflect the new value.
As an example, consider what happens when you copy an ArrayList, which is a reference type. Consider the program ReferenceCopy. (The download file contains this program and the others mentioned in the article.) This program makes a copy of a Course. The Course class consists of a title and a collection of students.
// Course.cs using System; using System.Collections; public class Course { public string Title; public ArrayList Roster; public Course(string title) { Title = title; Roster = new ArrayList(); } public void AddStudent(string name) { Roster.Add(name); } public void Show(string caption) { Console.WriteLine("-----{0}-----", caption); Console.WriteLine("Course : {0} with {1} students", Title, Roster.Count); foreach (string name in Roster) { Console.WriteLine(name); } } }
The test program constructs a Course instance c1 and then makes a copy c2 by the straight assignment c2 = c1. Now we get two references to the same object, and if we make any change through the first reference, we will see the same change through the second reference. The test program illustrates such an assignment.
// ReferenceCopy.cs using System; using System.Collections; public class ReferenceCopy { private static Course c1, c2; public static void Main() { Console.WriteLine("Copy is done via c2 = c1"); InitializeCourse(); c1.Show("original"); c2 = c1; c2.Show("copy"); c2.Title = ".NET Programming"; c2.AddStudent("Charlie"); c2.Show("copy with changed title and new student"); c1.Show("original"); } private static void InitializeCourse() { c1 = new Course("Intro to C#"); c1.AddStudent("John"); c1.AddStudent("Mary"); } }
We initialize with the title "Intro to C#" and two students. We make the assignment c2 = c1 and then change the title and add another student for c2. We then show both c1 and c2, and we see that both reflect both of these changes. Here is the output:
Copy is done via c2 = c1 -----original----- Course : Intro to C# with 2 students John Mary -----copy----- Course : Intro to C# with 2 students John Mary -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : .NET Programming with 3 students John Mary Charlie
Shallow Copy and Deep Copy
A struct in C# automatically implements a memberwise copy, sometimes known as a shallow copy. The object root class has a protected method, MemberwiseClone, which will perform a memberwise copy of members of a class.
If one or more members of a class are of a reference type, this memberwise copy may not be good enough. The result will be two references to the same data, not two independent copies of the data. To actually copy the data itself and not merely the references, you will need to perform a deep copy. Deep copy can be provided at either the language level or the library level. In C++, deep copy is provided at the language level through a copy constructor. In C#, deep copy is provided by the .NET Framework through a special interface, ICloneable, which you can implement in your classes in order to enable them to perform deep copy.
MemberwiseClone
The next way we will illustrate doing a copy is with a memberwise copy, which can be accomplished using the MemberwiseClone method of object. Because this method is protected, we cannot call it directly from outside our Course class. Instead, in Course, we define a method, ShallowCopy, which is implemented using MemberwiseClone. Our sample solution is ShallowCopy.
// Course.cs using System; using System.Collections; public class Course { ... public Course ShallowCopy() { return (Course) this.MemberwiseClone(); } ... }
Here is the modified test program, which calls the ShallowCopy method. Again, we change the title and a student in the second copy.
// ShallowCopy.cs ... public static void Main() { Console.WriteLine( "Copy is done via MemberwiseClone"); InitializeCourse(); c1.Show("original"); c2 = c1.ShallowCopy(); c2.Show("copy"); c2.Title = ".NET Programming"; c2.AddStudent("Charlie"); c2.Show("copy with changed title and new student"); c1.Show("original"); } ...
Here is the output. Now the Title field has its own independent copy, but the Roster collection is just copied by reference, so each copy refers to the same collection of students.
Copy is done via c2 = c1.ShallowCopy() ... -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : Intro to C# with 3 students John Mary Charlie
Using ICloneable
The final version of our program relies on the fact that our Course class supports the ICloneable interface, and implements the Clone method. To clone the Roster collection, we use the fact that ArrayList implements the ICloneable interface, as discussed earlier in the chapter. Note that the Clone method returns an object, so we must cast to ArrayList before assigning to the Roster field. Our sample solution is DeepCopy.
// Course.cs using System; using System.Collections; public class Course : ICloneable { ... public object Clone() { Course course = new Course(Title); course.Roster = (ArrayList) Roster.Clone(); return course; } }
Here is the third version of the test program, which calls the Clone method. Again, we change the title and a student in the second copy.
// DeepCopy.cs ... public static void Main() { Console.WriteLine( "Copy is done via c2 = c1.Clone()"); InitializeCourse(); c1.Show("original"); c2 = (Course) c1.Clone(); c2.Show("copy"); c2.Title = ".NET Programming"; c2.AddStudent("Charlie"); c2.Show("copy with changed title and new student"); c1.Show("original"); } ...
Here is the output from the third version of the program. Now we have completely independent instances of Course. Each has its own title and set of students.
Copy is done via c2 = c1.Clone() ... -----copy with changed title and new student----- Course : .NET Programming with 3 students John Mary Charlie -----original----- Course : Intro to C# with 2 students John Mary