|Deep C# - Passing Parameters|
|Written by Mike James|
|Thursday, 19 November 2015|
Page 4 of 4
If all you require is a shallow clone then you can simply use the MemberwiseClone which is inherited from object.
This is a protected non-virtual method that reduces its usefulness a little.
To use it you simply add a clone or copy method to your reference type:
If you now use:
you will discover, as always, that a.x has changed to be 20.
However if you use:
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:
Now when you clone the object you clone the reference to StringBuilder but not the StringBuilder object it references:
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 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:
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:
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:
a deep clone of a can be created using:
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.
or email your comment to: email@example.com
|Last Updated ( Thursday, 19 November 2015 )|