Variable Scope


Throughout the last section, you may have been wondering why exchanging data with functions is necessary. The reason is that variables in C# are only accessible from localized regions of code. A given variable is said to have a scope from where it is accessible.

Variable scope is an important subject and one best introduced with an example. The following Try It Out illustrates a situation in which a variable is defined in one scope, and an attempt to use it is made in a different scope.

Try It Out – Defining and Using a Basic Function

image from book
  1. Make the following changes to Ch06Ex01 in Program.cs:

    class Program {    static void Write()    { Console.WriteLine("myString = {0}", myString);    }        static void Main(string[] args)    { string myString = "String defined in Main()";       Write();       Console.ReadKey();    } }
  2. Compile the code, and note the error and warning that appear in the task list:

    The name 'myString' does not exist in the current context The variable 'myString' is assigned but its value is never used

How It Works

So, what went wrong? Well, the variable myString defined in the main body of your application (the Main() function) isn't accessible from the Write()function.

The reason for this inaccessibility is that variables have a scope within which they are valid. This scope encompasses the code block that they are defined in and any directly nested code blocks. The blocks of code in functions are separate from the blocks of code from which they are called. Inside Write() the name myString is undefined, and the myString variable defined in Main() is out of scope — it can only be used from within Main().

In fact, you can have a completely separate variable in Write() called myString. Try modifying the code as follows:

class Program {    static void Write()    { string myString = "String defined in Write()"; Console.WriteLine("Now in Write()");       Console.WriteLine("myString = {0}", myString);    }        static void Main(string[] args)    {       string myString = "String defined in Main()";       Write(); Console.WriteLine("\nNow in Main()"); Console.WriteLine("myString = {0}", myString);       Console.ReadKey();    } }

This code does compile, and it results in the output shown in Figure 6-4.

image from book
Figure 6-4

The operations performed by this code are:

  • Main() defines and initializes a string variable called myString.

  • Main() transfers control to Write().

  • Write() defines and initializes a string variable called myString, which is a different variable to the myString defined in Main().

  • Write() outputs a string to the console containing the value of myString as defined in Write().

  • Write() transfers control back to Main().

  • Main() outputs a string to the console containing the value of myString as defined in Main().

Variables whose scope covers a single function in this way are known as local variables. It is also possible to have global variables, whose scope covers multiple functions. Modify the code as follows:

class Program { static string myString;        static void Write()    {       string myString = "String defined in Write()";       Console.WriteLine("Now in Write()"); Console.WriteLine("Local myString = {0}", myString); Console.WriteLine("Global myString = {0}", Program.myString);    }        static void Main(string[] args)    {       string myString = "String defined in Main()"; Program.myString = "Global string";       Write();       Console.WriteLine("\nNow in Main()"); Console.WriteLine("Local myString = {0}", myString); Console.WriteLine("Global myString = {0}", Program.myString);       Console.ReadKey();    } }

The result is now as shown in Figure 6-5.

image from book
Figure 6-5

Here, you have added another variable called myString, this time further up the hierarchy of names in the code. This variable is defined as follows:

static string myString;

Note that again you require the static keyword here. Again, I'm not going to say any more about this at this point other than that in this type of console application, you must use either the static or const keyword for global variables of this form. If you want to modify the value of the global variable, you need to use static, because const prohibits the value of the variable changing.

To differentiate between this variable and the local variables in Main() and Write() with the same names, you have to classify the variable name using a fully qualified name, as introduced in Chapter 3. Here, you refer to the global version as Program.myString. Note that this is only necessary when you have global and local variables with the same name, if there was no local myString variable, you could simply use myString to refer to the global variable, rather than Program.myString. When you have a local variable with the same name as a global variable, the global variable is said to be hidden.

The value of the global variable is set in Main() with

Program.myString = "Global string";

and accessed in Write() with:

Console.WriteLine("Global myString = {0}", Program.myString);

Now, you may be wondering why you shouldn't just use this technique to exchange data with functions, rather than the parameter passing you saw earlier; there are indeed situations in which this is the preferable way to exchange data, but there are just as many (if not more) where it isn't. The choice of whether to use global variables depends on the intended use of the function in question. The problem with using global variables is that they are generally unsuitable for "general-purpose" functions, which are capable of working with whatever data you supply, not just data in a specific global variable. You look at this in more depth a little later.

image from book

Variable Scope in Other Structures

Before moving on, it is worth noting that one of the points made in the last section has consequences above and beyond variable scope between functions. I stated that the scope of variables encompasses the code block that they are defined in and any directly nested code blocks. This also applies to other code blocks, such as those in branching and looping structures. Consider the following code:

 int i; for (i = 0; i < 10; i++) { string text = "Line " + Convert.ToString(i); Console.WriteLine("{0}", text); } Console.WriteLine("Last text output in loop: {0}", text); 

Here, the string variable text is local to the for loop. This code won't compile, because the call to Console.WriteLine() that occurs outside of this loop attempts to use the variable text, which is out of scope outside of the loop. Try modifying the code as follows:

int i; string text; for (i = 0; i < 10; i++) { text = "Line " + Convert.ToString(i);    Console.WriteLine("{0}", text); } Console.WriteLine("Last text output in loop: {0}", text);

This code will also fail. The reason for this is that variables must be declared and be initialized before use, and text is only initialized in the for loop. The value assigned to text is lost when the loop block is exited. However, you can also make the following change:

int i; string text = ""; for (i = 0; i < 10; i++) {    text = "Line " + Convert.ToString(i);    Console.WriteLine("{0}", text); } Console.WriteLine("Last text output in loop: {0}", text);

This time text is initialized outside of the loop, and you have access to its value. The result of this simple code is shown in Figure 6-6.

image from book
Figure 6-6

Here, the last value assigned to text in the loop is accessible from outside the loop.

As you can see, this topic requires a bit of work to come to grips with. It is not immediately obvious why, in the light of the earlier example, text doesn't retain the empty string it is assigned before the loop in the code after the loop.

The explanation for this behavior concerns the memory allocation for the text variable, and indeed any variable. Simply declaring a simple variable type doesn't result in very much happening. It is only when values are assigned to the variable that values are allocated a place in memory to be stored. When this allocation takes place inside a loop, the value is essentially defined as a local value and goes out of scope outside of the loop.

Even though the variable itself isn't localized to the loop, the value it contains is. However, assigning a value outside of the loop ensures that the value is local to the main code, and it is still in scope inside the loop. This means that the variable doesn't go out of scope before the main code block is exited, so you have access to its value outside of the loop.

Luckily for you, the C# compiler will detect variable scope problems, and responding to the error messages it generates certainly helps you to understand the topic of variable scope.

As a final note, you should be aware of best practice. In general, it is worth declaring and initializing all variables before any code blocks that use them. An exception to this is when you declare looping variables as part of a loop block, for example:

 for (int i = 0; i < 10; i++) { ... } 

Here i is localized to the looping code block, but this is fine, because you will rarely require access to this counter from external code.

Parameters and Return Values versus Global Data

In this section, you take a closer look at exchanging data with functions via global data and via parameters and return values. To recap, consider the following code:

class Program { static void ShowDouble(ref int val) { val *= 2; Console.WriteLine("val doubled = {0}", val); }        static void Main(string[] args)    { int val = 5; Console.WriteLine("val = {0}", val); ShowDouble(ref val); Console.WriteLine("val = {0}", val);    } }
Note

Note that this code is slightly different from the code you saw earlier in this chapter, when you used the variable name myNumber in Main(). This illustrates the fact that local variables can have identical names and yet not interfere with each other. It also means that the two code samples shown here are more similar, allowing you to focus more on the specific differences without worrying about variable names.

And compare it with this code:

class Program { static int val;     static void ShowDouble() { val *= 2; Console.WriteLine("val doubled = {0}", val); }        static void Main(string[] args)    { val = 5; Console.WriteLine("val = {0}", val); ShowDouble(); Console.WriteLine("val = {0}", val);    } }

The results of both of these showDouble() functions are identical.

Now, there are no hard and fast rules for using one method rather than another, and both techniques are perfectly valid. However, there are some guidelines you might like to consider.

To start with, as mentioned when this topic was first introduced, the ShowDouble() version that uses the global value will only ever use the global variable val. To use this version, you must use this global variable. This limits the versatility of the function slightly and means that you must continuously copy the global variable value into other variables if you intend to store the results. In addition, global data might be modified by code elsewhere in your application, which could cause unpredicted results (values might change without you realizing this until it's too late).

However, this loss of versatility can often be a bonus. There are times when you only ever want to use a function for one purpose, and using a global data store reduces the possibility that you will make an error in a function call, perhaps passing it the wrong variable.

Of course, it could also be argued that this simplicity actually makes your code more difficult to understand. Explicitly specifying parameters allows you to see at a glance what is changing. If you see a call that reads myFunction(val1, out val2), you instantly know that val1 and val2 are the important variables to consider and that val2 will be assigned a new value when the function is completed. Conversely, if this function took no parameters, you would be unable to make any assumptions as to what data it manipulated.

Finally, it should be remembered that using global data isn't always possible. Later on in this book, you will see code written in different files and/or belonging to different namespaces communicating with each other via functions. In cases such as this, the code is often separated to such a degree that there is no obvious choice for a global storage location.

So, to summarize, feel free to use either technique to exchange data. I would, in general, urge you to use parameters rather than global data, but there are certainly cases where global data might be more suitable, and it certainly isn't an error to use this technique.




Beginning Visual C# 2005
Beginning Visual C#supAND#174;/sup 2005
ISBN: B000N7ETVG
EAN: N/A
Year: 2005
Pages: 278

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net