Passing a reference type by value

To complete the list of ways of passing parameters we need to consider the task of passing a reference type by value. That is rather than passing a reference to an object on the stack we want to make a copy of the object and pass this instead.

If you think about this for a moment there is little difference between this and passing a reference to a copy of the object on the heap. In both cases you can make changes to the parameter passed into the method without changing the original object the only real difference is where the copy of the object is stored, on the stack or the heap.

So to pass a reference type by value we simply have to make a copy of the object. This sounds easy but it practice it turns out to be difficult and sometimes impossible.

The problem is that it isn’t clear what constitute a clone of an object. A shallow clone is a copy of the object that simply copies all of the data values in its fields including value types and reference types. Of course if there are any reference types then all a shallow clone copies are the references to other objects on the heap and these are clearly going to be shared by the original object and any shallow clones.

A deep clone on the other hand makes a copy of every object referenced by the original object this produces a truly independent copy of the original object. You can see the deep cloning could become very difficult as objects and sub objects and sub-sub-objects have to be cloned. In addition there is the possibility that one fo the object might be unique - either a singleton class or a wrapper for some unique system resource like a printer - and then a clone, even a shallow clone doesn’t make a great deal of sense.

If all you require is a shallow clone then you can simply use the MemberwiseClone which is inherited from object. This is a protected method non-virtual method that reduces its usefulness a little. To use it you simply add a clone or copy method to you reference type:

you will discover, as always, that a.x has changed to be 20. However if you use:

PointR a = new PointR();PointR b = a.clone();b.x = 20;

you will discover that a.x is still set to its default value of zero. The clone method has created a completely new object on the heap for b to reference. To see that this new object is a shallow copy is fairly easy - simply add a field that is a reference type. a StringBuilder for example:

Notice the way that we have to create a new StringBuilder object before we can use the field as discussed in the previous Chapter a reference type has to be initialise in this way before use.

If you examine the contents of b.z and a.z you will discover that both are set to “test1test2" but b.x and a.x are still hold different values. You might be wondering why StringBuilder was used as an example of a reference type and not just String? The answer is that String is an immutable reference type which means that each time you modify a String a new object is created. If you change the example to use a string you will find that although only the reference to the string is cloned assigning a new value to b.z creates a new String object leaving a.z still referencing the old version. Thus an immutable object behaves as if it has been passed by value even when its really been passed by reference. More on Strings and immutable objects later.

The Iclonable interface was introduced to provided a standard way of copying object but this has mostly been ignored because of the problem of knowing if its single member, the clone method, was to perform a shallow or a deep clone. In addition the clone method returned an object type forcing the user to caste to the correct type before using the clone. In addition by its very nature cloning can cause problems in derived classes and it needs to be treated with caution.

If you want to create a deep clone then the simplest solution is to use the serialization services provided by the framework. Serialization is intended to be used to convert an object into binary, perhaps with a choice of formatting, so that it can be saved on disk or transmitted to another location. Deseralization can then be used to read the stream of bits and reconstruct the object. It might sound complicated but the framework really does all the work for us. The only complication is that we need a stream as well as a formatter object to make it all work and hence need two additional using statements:

Any class that you are going to serialize has to be marked with the serializable attribute. This is simply a check that you have considered the implications of attempting to serialize the class. In this case we make the PointR class as serializable:

[Serializable]public class PointR

Now we can make a clone by first serializing the original object to a memory stream:

You could use a file stream if the object is very large but this would clearly slow things down. At this point we have a binary representation of all of the non-static fields in the object that a references. The serialization is a deep copy because the BinaryFormatter walks the object graph following references and serializing any objects it finds - assuming that these are also marked as serializable. If an object that isn’t marked as serializable is encountered then the serialization fails.

To deserialize the stream we simply reset it to the start and use the Deserialize method:

MS.Position = 0;PointR c = (PointR) BF.Deserialize(MS);

At this point we have a deep clone of the original object referenced by a on referenced by c. Any changes to the fields of c, including the StringBuilder in z, have no effect on a.

Of course it makes sense to package the serialize/deserialize in a method that is part of the class being serialized. For example, if we add the following method to PointR:

Notice the use of this within the method to ensure that it is the current object that is cloned.

You can control the serialisation process by marking fields that should not be serialized as [NonSerializedAttribute ]. For even more control you can implement the ISerializable interface to completely customise the conversion process. Of course there are other uses for serilization and it is worth knowing that there is also a SoapFormatter class that can be used in place of the BinaryFormatter.