A variable represents a storage location that has a modifiable value. A variable can be a local variable, parameter (value, ref, or out), field (instance or static), or array element.
The stack and the heap are the places where variables and constants reside. Each has very different lifetime semantics.
The stack is a block of memory for storing local variables and parameters. The stack automatically grows and shrinks as a function is entered and exited. Consider the following method (to avoid distraction, input argument checking is ignored):
static int Factorial (int x) { if (x == 0) return 1; return x * Factorial (x-1); }
This method is recursive, meaning that it calls itself. Each time the method is
entered, a new int
is allocated on the stack, and
each time the method exits, the int
is
deallocated.
The heap is a block of memory in which objects (i.e., reference
type instances) reside. Whenever a new object is created, it is allocated on the heap,
and a reference to that object is returned. During a program’s execution, the heap
starts filling up as new objects are created. The runtime has a garbage collector that periodically deallocates objects
from the heap, so your computer does not run out of memory. An object is eligible for
deallocation as soon as nothing references it. In the following example, the StringBuilder
object is created on the heap, while the
sb
reference is created on the stack:
static void Test() { StringBuilder sb = new StringBuilder(); Console.WriteLine (sb.Length); }
After the Test
method finishes, sb
pops off the stack, and the StringBuilder
object is no longer referenced, so it becomes eligible for
garbage collection.
Value type instances (and object references) live wherever the variable was declared. If the instance was declared as a field within an object, or as an array element, that instance lives on the heap.
You can’t explicitly delete objects in C#, as you can in C++. An unreferenced object is eventually collected by the garbage collector.
You can’t explicitly delete objects in C#, as you can in C++. An unreferenced object is eventually collected by the garbage collector.The heap is also used to store static fields and constants. Unlike objects allocated on the heap (which can get garbage collected), these will live until the application domain is torn down.
C# enforces a definite assignment policy. In practice, this means that outside of an
unsafe
context, it’s impossible to access uninitialized memory. Definite assignment has three
implications:
Local variables must be assigned a value before they can be read.
Function arguments must be supplied when a method is called.
All other variables (such as fields and array elements) are automatically initialized by the runtime.
For example, the following code results in a compile-time error:
static void Main() { int x; Console.WriteLine (x); // compile-time error }
Fields and array elements are automatically initialized with the default values for their type. The following code outputs
0
because array elements are implicitly assigned to
their default
values:
static void Main() { int[] ints = new int[2]; Console.WriteLine (ints[0]); // 0 }
The following code outputs 0
because fields are
implicitly assigned to a default value:
class Test { static int x; static void Main() { Console.WriteLine (x); } // 0 }
All type instances have a default value. The default value for the primitives is the result of a bitwise-zeroing of memory.
The default value in a custom value type (i.e., struct) is the same as the default value for each field defined by the custom type.
A method has a sequence of parameters. Parameters define the set of arguments that must be provided for that
method. In this example, the method Foo
has a single
parameter named p
, of type int
:
static void Foo (int p) { p = p + 1; // increment p by 1 Console.WriteLine(p); // write p to screen } static void Main() { Foo(8); }
You can control how parameters are passed with the ref
and out
modifiers.
Parameter modifier |
Passed by |
Variable must be definitely assigned |
---|---|---|
None |
Value |
Going in |
|
Reference |
Going in |
|
Reference |
Going out |
By default, arguments in C# are passed by value, which is by far the most common case. This means a copy of the value is created when passed to the method:
class Test { static void Foo (int p) { p = p + 1; // Increment p by 1 Console.WriteLine (p); // Write p to screen } static void Main( ) { int x = 8; Foo (x); // Make a copy of x Console.WriteLine (x); // x will still be 8 } }
Assigning p
a new value does not change the
contents of x
because
p
and x
reside
in different memory locations.
Passing a reference
type object by value copies the reference, but not the object. In
the following example, Foo
sees the same StringBuilder
object that Main
instantiated, but has an independent reference to it. In other words,
sb
and fooSB
are
separate variables that reference the same StringBuilder
object:
class Test { static void Foo (StringBuilder fooSB) { fooSB.Append ("test"); fooSB = null; } static void Main( ) { StringBuilder sb = new StringBuilder( ); Foo (sb); Console.WriteLine (sb.ToString( )); // test } }
Because fooSB
is a copy of a
reference, setting it to null
doesn’t make sb
null. (If, however, fooSB
was declared and called with the ref
modifier, sb
would become null.)
To pass by reference, C# provides the ref
parameter modifier. In the following example, p
and x
refer to the same memory
locations:
class Test { static void Foo (ref
int p) { p = p + 1; // increment p by 1 Console.WriteLine(p); // write p to screen } static void Main( ) { int x = 8; Foo (ref
x); // Ask Foo to deal directly // with x Console.WriteLine(x); // x is now 9 } }
Now assigning p
a new value changes the contents
of x
. Notice how the
ref
modifier is required both when writing and when
calling the method. This makes it very clear what’s going on.
An out
argument is like a ref
argument, except it:
Need not be assigned before it goes into the function
Must be assigned before it comes out of the function
The out
modifier is most commonly used to get
multiple return
values back from a method. Like a ref
parameter, an
out
parameter is passed by reference.
When you pass an argument by reference, you alias the storage location of an
existing variable, rather than creating a new storage location. In the following
example, the variables x
and y
represent the same instance:
class Test { static int x; static void Main( ) { Foo (out x); } static void Foo (out int y) { Console.WriteLine (x); // x is 0 y = 1; // Mutate y Console.WriteLine (x); // x is 1 } }
The params
modifier may be specified on the last
parameter of a method so that the method accepts any number of parameters
of a particular type. The parameter type must be declared as an array. For
example:
static int Sum (params int[] ints) { int sum = 0; for (int i = 0; i < ints.Length; i++) sum += ints[i]; // increase sum by ints[i] return sum; } static void Main( ) { int total = Sum (1, 2, 3, 4); Console.WriteLine (total); // 10 }
You can also supply a params
argument as an
ordinary array. The first line in Main
is
semantically equivalent to this:
int total = Sum (new int[] { 1, 2, 3, 4 } );
It is often the case that you declare and initialize a variable in one step. If the
compiler is able to infer the type from the initialization expression, you can use the
word var
in place of the type declaration. For
example:
var x = 5; var y = "hello"; var z = new System.Text.StringBuilder(); var req = (System.Net.FtpWebRequest) System.Net.WebRequest.Create ("...");
This is precisely equivalent to:
int
x = 5;string
y = "hello";System.Text.StringBuilder
z = new System.Text.StringBuilder( );System.Net.FtpWebRequest
req = (System.Net.FtpWebRequest) System.Net.WebRequest.Create ("...");
Because of this direct equivalence, implicitly typed variables are statically typed. For example, the following generates a compile-time error:
var x = 5; x = "hello"; // Compile-time error; x is of type int
var
can decrease code readability in the case
you can’t deduce the type purely from looking at the variable
declaration. For example:
Random r = new Random(); var x = r.Next();
What type is x
?
In the upcoming section “Anonymous Types (C# 3.0),” we describe a scenario in which
the use of var
is mandatory.