CHAPTER 12

image

Variable Scoping and Definite Assignment

In C#, local variables must be given names that allow all variables to be uniquely identified throughout the method. Consider the following:

using System;
class MyObject
{
    public MyObject(int x)
    {
       x = x;
    }
    int x;
}

Since the compiler looks up parameters before it looks up member variables, the constructor in this example does not do anything useful; it copies the value of parameter x to parameter x.1 You can fix this by adding this. to the front of the name that you want to refer to the member variable.2

using System;
class MyObject
{
    public MyObject(int x)
    {
       this.x = x;
    }
    int x;
}

In the following situation, it’s unclear what x means inside the for loop, and there’s no way to make the meaning clear. It is therefore an error.

// error
using System;
class MyObject
{
    public void Process()
    {
       int x = 12;
       for (int y = 1; y < 10; y++)
       {
            int x = 14;
                // which x do we mean?
            Console.WriteLine("x = {0}", x);
       }
    }
}

C# has this restriction to improve code readability and maintainability. It is possible to use the same variable multiple times in different scopes.

using System;
class MyObject
{
    public void Process()
    {
       for (int y = 1; y < 10; y++)
       {
            int x = 14;
            Console.WriteLine("x = {0}", x);
       }

       for (int y = 1; y < 10; y++)
       {
            int x = 21;
            Console.WriteLine("x = {0}", x);
       }
    }
}

This is allowed because there is no ambiguity present; it is always clear which x is being used.

Definite Assignment

Definite assignment rules prevent the value of an unassigned variable from being observed. Suppose the following is written:

// error
using System;
class Test
{
    public static void Main()
    {
       int n;
       Console.WriteLine("Value of n is {0}", n);
    }
}

When this is compiled, the compiler will report an error because the value of n is used before it has been initialized.

Similarly, operations cannot be done with a class variable before the variable is initialized.

// error
using System;
class MyClass
{
    public MyClass(int value)
    {
       m_value = value;
    }
    public int Calculate()
    {
       return m_value * 10;
    }
    public int m_value;
}
class Test
{
    public static void Main()
    {
       MyClass mine;

       Console.WriteLine("{0}", mine.m_value);       // error
       Console.WriteLine("{0}", mine.Calculate());    // error
       mine = new MyClass(12);
       Console.WriteLine("{0}", mine.m_value);       // okay now...
    }
}

Structs work slightly differently when definite assignment is considered. The runtime will always make sure they’re zeroed out, but the compiler will still check to make sure they’re initialized to a value before they’re used.

A struct is initialized either through a call to a constructor or by setting all the members of an instance before it is used.

using System;
struct Complex
{
    public Complex(float real, float imaginary)
    {
       m_real = real;
       m_imaginary = imaginary;
    }
    public override string ToString()
    {
       return String.Format("({0}, {1})", m_real, m_imaginary);
    }
    public float m_real;
    public float m_imaginary;
}
class Test
{
    public static void Main()
    {
       Complex myNumber1;
       Complex myNumber2;
       Complex myNumber3;

       myNumber1 = new Complex();
       Console.WriteLine("Number 1: {0}", myNumber1);

       myNumber2 = new Complex(5.0F, 4.0F);
       Console.WriteLine("Number 2: {0}", myNumber2);

       myNumber3.m_real = 1.5F;
       myNumber3.m_imaginary = 15F;
       Console.WriteLine("Number 3: {0}", myNumber3);
    }
}

In the first section, myNumber1 is initialized by the call to new. Remember that for structs, there is no default constructor, so this call doesn’t do anything; it merely has the side effect of marking the instance as initialized.

In the second section, myNumber2 is initialized by a normal call to a constructor.

In the third section, myNumber3 is initialized by assigning values to all members of the instance. Obviously, this can be done only if the members are accessible.

Definite Assignment and Class Members

C# does not require definite assignment of class members before use. Consider the following:

class AlwaysNullName
{
    string m_name;

    string GetName()
    {
       return m_name;
    }
}

The value of m_name will be null when GetName() is called. The compiler will provide a helpful warning in this situation, but there are other situations that it cannot detect.

Definite Assignment and Arrays

Arrays work a bit differently for definite assignment. For arrays of both reference and value types (classes and structs), an element of an array can be accessed, even if it hasn’t been initialized with a value.

For example, suppose there is an array of Complex.

using System;
struct Complex
{
    public Complex(float real, float imaginary)
    {
       m_real = real;
       m_imaginary = imaginary;
    }
    public override string ToString()
    {
       return(String.Format("({0}, {1})", m_real, m_imaginary));
    }

    public float m_real;
    public float m_imaginary;
}

class Test
{
    public static void Main()
    {
       Complex[] arr = new Complex[10];
       Console.WriteLine("Element 5: {0}", arr[5]);       // legal
    }
}

Because of the operations that might be performed on an array—such as Reverse()—the compiler can’t track definite assignment in all situations, and it could lead to spurious errors. It therefore doesn’t try.

1 The C# compiler will flag this and ask you whether you wanted to do something different.

2 My preference is never to use a different name for the member variable so that there is no possibility for confusion.

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

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