Saturday 2 May 2015

C# Parameters, Value or Reference? -Explained.

C# Pass by Reference vs value

C# is one of my favorite languages with a chief architect that is also my favorite language designer. After working with C# for some time, I know C# pretty well however being an expert in it is something I am yet to achieve. There are so many things to remember about it that you really need a reference nearby. Alternatively you can browse blogs by blog posters posting their experience with the language! The other equation of programming is good design patterns so this brings me to another part of C# that I like. It makes it quite intuitive to implement design patterns such as three-tier Programming Architecture.

Recently I had a discussion with someone about whether C# passed by reference or value. It isn't a simple an answer as one would expect at the onset, however I'll explain with examples.

Firstly what does the C# specification say about parameter passing?
C# specification 1.6.6 (Version 5.0) states that parameters are passed by value. This is where the confusion arises.

Consider:
private void PassByValue(int index)
{
    index = 10;
}
What happens in the above code? The original Index remains unchanged but the index inside the method changes to 10. This is the correct behavior as outlined in the C# Specification.

The confusion arises when we do this:
private void PassByValue(int[] index)
{
    index[0] = 10;
}
What happens in the above code? In the first example and according to the C# specification if not well understood would have you believe that  the array "index" was passed by value, correct. That changing the value inside the method for cell one will not change the original, which is incorrect!

So what happens is that C# passes everything that is not a primitive, implicitly, as reference. If I just left it at that, then that statement is not entirely correct and that is where the confusion arises. When is a value not a value? When it is a reference! The array is actually a reference to memory location and when you pass the array to the method, you are passing the "value" of that reference to the method. Hence when you change a "cell" inside the array, you are changing the value pointed to by the reference. Since the original reference is not broken this change is reflected in the original. However if you changed the "value" i.e you changed the reference which was passed by value, you are breaking that therefore the original remains unchanged and you are working on a "new" reference.
Hence if you did this:
private void PassByValue(int[] index)
{
    index = new int[1];
    index[0] = 10;
}
In the above code, the reference to the original is broken. You are now working on a new reference and changing this will have no effect on the original. This does not break the C# specification because you passed by value, except in this case that value is the reference. Also the original is not left "dangling" simply because in C#, much like java, an object is never GC'ed if there is still a reference to it (the calling scope in the above example).

In order to maintain the changes you make to an object in C# in the original passed from outside the method, you need to tell C# that you want to pass a reference to a reference and that is done using the ref keyword.
The code below:
private void PassByReference(ref int[] index)
{
    index = new int[1];
    index[0] = 10;
}
The code above will reassign the reference the original is pointing to, to the new location created in-scope of the method it was passed to. So cell 0 of the original will show the value changed to 10.
This is also another advantage of C# over C++. If you did this in C++ you will get a "dangling pointer" where by once the method goes out of scope, the "new" object created inside the method is destroyed, but the original pointer is changed to point to nothing. This also highlights why C# is considered a "safe" language (Chapter 18, Paragraph 1, C# Language specification version 5.0).

If you do not understand this behavior correctly, you will either abuse the use of "ref" and "out" Modifiers or you will create a bug in your code and have trouble finding out and even understanding what went wrong.

Here is another example:
//Class for testing.
public class DataStorage
{
    public string FirstName; //Field
    public string LastName { get;  set; } //Property
}

//The test Method
private void TestCase(DataStorage ds)
{
    ds.FirstName = "John";
    ds.LastName = "Smith";
}
In the above example, the DataStorage instance passed by the caller to the TestCase Method will have its members changed to reflect the new values set by the method. If you print the original in its respective order, it will print "John Smith". It does not matter whether the members of the class are fields, properties, other class instances, they will all behave exactly the same way. Once again, it does not break the C# language specification.

This can also cause problems with 'Deep Copy'. If you created a copy method that that copies all of an objects members and some of them are objects, then the original and the copy will share those objects members. So a Deep copy may end up actually being a shallow copy - oho! a trap! A good way to avoid that is to do a Deep Copy as a clone copy where there is a bit-by-bit copy of the entire object into a new object. This means new references and new locations. It isn't hard to do and you can achieve this in 10-13 lines of code regardless of how many members you have in the object being copied! I use bit-by-bit on any of my objects I want a deep copy on. In saying that, however, I  have rarely needed to use deep copy and good program design can reduce or eliminate its needs. If you find you need to make a Deep copy, maybe it is time to re-evaluate your design and refactor it.

So to summarise, primitives have their values passed, where as objects have their reference passed as value.  In order to assign a new reference to a closure variable (external variable passed to the method) you have to explicitly tell C# this is the intended behavior.


No comments :

Post a Comment