Many people have become fairly confused about how parameters are passed in C#, particularly with regard to reference types. This page should help to clear up some of that confusion. If you have any suggestions for how it can be made clearer, please mail me.
Microsoft also has a good page about this topic (which I believe uses exactly the same terminology as this page - let me know if they appear to disagree).
Preamble: what is a reference type?
In .NET (and therefore C#) there are two main sorts of type: reference types and value types. They act differently, and a lot of confusion about parameter passing is really down to people not properly understanding the difference between them. Here's a quick explanation:
A reference type is a type which has as its value a reference to the appropriate data rather than the data itself. For instance, consider the following code:
StringBuilder sb = new StringBuilder();
(I have used StringBuilder
as a random example
of a reference type - there's nothing special about it.)
Here, we declare a variable sb
, create a new
StringBuilder
object, and assign to sb
a reference to
the object. The value of sb
is not the object itself, it's
the reference. Assignment involving reference types is simple - the value which
is assigned is the value of the expression/variable - i.e. the reference.
This is demonstrated further in this example:
StringBuilder first = new StringBuilder();
StringBuilder second = first;
Here we declare a variable first
, create a new
StringBuilder
object, and assign to first
a reference
to the object. We then assign to second
the value of first
.
This means that they both refer to the same object. They are still, however,
independent variables themselves. Changing the value of first
will
not change the value of second
- although while their values are
still references to the same object, any changes made to the object through the
first
variable will be visible through the second
variable.
Here's a demonstration of that:
StringBuilder first = new StringBuilder();
StringBuilder second = first;
first.Append ("hello");
first = null;
Console.WriteLine (second);
Output:
Here, we declare a variable first
, create a new
StringBuilder
object, and assign to first
a reference
to the object. We then assign to second
the value of first
.
We then call the Append
method on this object via the reference held
in the first
variable. After this, we set the first
variable
to null
(a value which doesn't refer to any object). Finally,
we print out the results of calling the ToString
method on the
StringBuilder object via the reference held in the second
variable.
hello
is displayed, demonstrating that even though the value of
first
has changed, the data within the object it used to refer to hasn't -
and second
still refers to that object.
Class types, interface types, delegate types and array types are all reference types.
Further preamble: what is a value type?
While reference types have a layer of indirection between the variable and the real data, value types don't. Variables of a value type directly contain the data. Assignment of a value type involves the actual data being copied. Take a simple struct, for example:
public struct IntHolder
{
public int i;
}
Wherever there is a variable of type IntHolder
, the value of that variable
contains all the data - in this case, the single integer value. An assignment copies
the value, as demonstrated here:
IntHolder first = new IntHolder();
first.i=5;
IntHolder second = first;
first.i=6;
Console.WriteLine (second.i);
Output:
Here, second.i
has the value 5,
because that's the value first.i
has when the assignment
second=first
occurs - the values in second
are independent of the values in first
apart from when the assignment
takes place.
Simple types (such as float
, int
, char
), enum
types and struct types are all value types.
Note that many types (such as string
appear in some ways to be
value types, but in fact are reference types. These are known as
immutable types. This means that once an instance has been
constructed, it can't be changed. This allows a reference type to act
similarly to a value type in some ways - in particular, if you
hold a reference to an immutable object, you can feel comfortable in
returning it from a method or passing it to another method, safe in the
knowledge that it won't be changed behind your back. This is why, for
instance, the string.Replace
doesn't change the string it
is called on, but returns a new instance with the new string data in -
if the original string were changed, any other variables holding a
reference to the string would see the change, which is very rarely what
is desired.
Constrast this with a mutable (changeable) type
such as ArrayList
- if a method returns the ArrayList
reference
stored in an instance variable, the calling code could then add items to the list
without the instance having any say about it, which is usually a problem. Having
said that immutable reference types act like value types, they are not
value types, and shouldn't be thought of as actually being value types.
For more information about value types, reference types, and where the data for each is stored in memory, please see my other article about the subject.
Checking you understand the preamble...
What would you expect to see from the code above if the declaration of the
IntHolder
type was as a class instead of a struct? If you don't understand
why the output would be 6
, please re-read both preambles and
mail me if it's still not clear - if you don't
get it, it's my fault, not yours, and I need to improve this page. If you do
understand it, parameter passing becomes very easy to understand - read on.
Comments