Deep C# - Passing Parameters |
Written by Mike James | |||||
Thursday, 19 November 2015 | |||||
Page 3 of 4
BoxingA value type is descended from object and hence can be cast to object - but object is a reference type and this raises the question of exactly what happens. The answer is that a new object containing the value type is created on the heap a process called boxing. For example suppose you have defined a method something like:
What happens when you pass a simple value type. As value types are descended from “object” so that there is no reason why you can’t pass an int say. The problem is that on the inside of the method the variable “x” is treated using reference semantics and if you pass a value type by value this is going to be a problem. Whenever you either explicitly or implicitly cast a value type to a reference type then the value type is converted to or “boxed” by its hidden reference type. For example, if you write:
and then call the method using:
then y points to the same int object that x does. A small confusion is that the dynamic type of both x and y is int, as a boxed type has the same type as the corresponding value type. As you can imagine to make this work involves at least the creation of internal reference to the int and this all takes time. To unbox a value type you have to use an explicit cast as in:
You can box and unbox all of the simple types and structs. There is one final twist to the story, however, which is another enforcement of value type semantics. When a value type is boxed a complete copy of the value is made and not just a reference to the value. For example, following:
what do you think the value of x is? If x is just a reference to I then it should be 20 but in fact it is still 10 because a copy of the value in I is made when the variable is boxed. The same sort of thing happens when you box a struct. For example:
In this is case when the assignment q=p is made p is boxed and a new copy of the data is created and so the assignment p.x=20 doesn’t change the value of q.x. Notice that every time you assign a value type a new object is created on the heap. So for example,
sets both a and b referencing the same object on the heap with the integer value 10. Following:
you might think that b now also points to an object on the heap containing 20 but the assignment creates a new object on the heap containing 20 and only a reference this. The result is that I has the value 10, a has the value 20 and b still has the value 10. Boxing and the reverse process unboxing can occur in situations when you don’t even notice it happening. The most used example of this is WriteLine which takes an object as its parameter. Hence when you use it to display a simple integer:
a boxing operation occurs. Within the WriteLine method i.ToString is called to obtain a displayable form and you can avoid the boxing operation by simply passing a string in the first place:
Obviously boxing an unboxing take time and have the potential to slow your program down and it should be avoided if possible. In the past it was difficult to avoid because of the need to use object types within classes that implemented data structures so that they could work with as many types of element as possible. For example, the ArrayList data structure has an Add method that accepts a parameter of type object. Hence if you build an ArrayList of integers say each one will be boxed as you use Add to add it to the data structure.
The same sort of problem arrises with any data structure that used object types to make sure it works with almost anything. The solution is not to implement data structures in this way but use a generic class that can be typed appropriately when you use it. Of course this doesn’t help if you need a data structure which stores a set of mixed types - in this case boxing and unboxing value types might be your only option. Passing a reference type by valueTo 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. |
|||||
Last Updated ( Thursday, 19 November 2015 ) |