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).
Note: Lee Richardson has written a complementary article to this one, particularly for those who learn well with pictures. Basically it illustrates the same points, but using pretty diagrams to show what's going on.
Table of contents
- Preamble: what is a reference type?
- Further preamble: what is a value type?
- Checking you understand the preamble...
- The different kinds of parameters
- Value parameters
- Reference parameters
- Output parameters
- Parameter arrays
- Mini-glossary
Preamble: what is a reference type?
StringBuilder sb = new StringBuilder(); |
StringBuilder first = new StringBuilder(); |
StringBuilder first = new StringBuilder(); |
(Download sample code) Output:
hello |
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?
public struct IntHolder |
IntHolder first = new IntHolder(); |
(Download sample code) Output:
5 |
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.
The different kinds of parameters
Value parameters
void Foo (StringBuilder x) |
(Download sample code) Output:
False |
The value of y
isn't changed just because x
is set to null
. Remember though that the value of a reference type variable is the reference - if two reference type variables refer to the same object, then changes to the data in that object will be seen via both variables. For example:
void Foo (StringBuilder x) |
(Download sample code) Output:
hello world |
After calling Foo
, the StringBuilder object that y
refers to contains "hello world", as in Foo
the data " world" was appended to that object via the reference held in x
.
Now consider what happens when value types are passed by value. As I said before, the value of a value type variable is the data itself. Using the previous definition of the struct IntHolder
, let's write some code similar to the above:
void Foo (IntHolder x) |
(Download sample code) Output:
5 |
When Foo
is called, x
starts off as a struct with value i=5
. Its i
value is then changed to 10. Foo
knows nothing about the variable y
, and after the method completes, the value in y
will be exactly the same as it was before (i.e. 5).
As we did earlier, check that you understand what would happen if IntHolder
was declared as a class instead of a struct. You should understand why y.i
would be 10 after calling Foo
in that case.
Reference parameters
void Foo (ref StringBuilder x) |
(Download sample code) Output:
True |
Here, because a reference to y
is passed rather than its value, changes to the value of parameter x
are immediately reflected in y
. In the above example, y
ends up being null
. Compare this with the result of the same code without the ref
modifiers.
Now consider the struct code we had earlier, but using reference parameters:
void Foo (ref IntHolder x) |
(Download sample code) Output:
10 |
The two variables are sharing a storage location, so changes to x
are also visible through y
, so y.i
has the value 10 at the end of this code.
Sidenote: what is the difference between passing a value object by reference and a reference object by value?You may have noticed that the last example, passing a struct by reference, had the same effect in this code as passing a class by value. This doesn't mean that they're the same thing, however. Consider the following code:
In the case where |
Output parameters
Output parameters are very similar to reference parameters. The only differences are:
- The variable specified on the invocation doesn't need to have been assigned a value before it is passed to the function member. If the function member completes normally, the variable is considered to be assigned afterwards (so you can then "read" it).
- The parameter is considered initially unassigned (in other words, you must assign it a value before you can "read" it in the function member).
- The parameter must be assigned a value before the function member completes normally.
void Foo (out int x) |
(Download sample code) Output:
10 |
Parameter arrays
void ShowNumbers (params int[] numbers) |
(Download sample code) Output:
1 2 3 |
In the first invocation, the variable x
is passed by value, as it's just an array. In the second invocation, a new array of ints is created containing the two values specified, and a reference to this array is passed.
Mini-glossary
Some informal definitions and summaries of terms:
From http://www.yoda.arachsys.com/csharp/parameters.html
0 nhận xét:
Post a Comment