Variables and Parameters

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

The stack and the heap are the places where variables and constants reside. Each has very different lifetime semantics.

Stack

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.

Heap

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.

Note

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.

Definite Assignment

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
	}

Default Values

All type instances have a default value. The default value for the primitives is the result of a bitwise-zeroing of memory.

Type

Defaultvalue

Reference

null

Numeric type or enum type

0

char type

''

bool type

false

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.

Parameters

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

ref

Reference

Going in

out

Reference

Going out

Passing arguments by value

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.)

The ref modifier

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.

Note

A parameter can be passed by reference or by value, regardless of whether the parameter type is a reference type or a value type.

The out modifier

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.

Implications of passing 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

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 } );

var: Implicitly Typed Local Variables (C# 3.0)

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

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset