Methods often need to be able to communicate with each other. One way to do this is by using shared variables.
A class can include other things besides methods. In particular, it can also include variable declarations. Of course, you can have variable declarations inside methods. Those are called local variables, because they can be used only within the method in which they are defined. However, you can also have variables that are not part of any method. To distinguish such variables from local variables, we call them member variables, since they are members of a class. Here's a brief code sample to illustrate the difference:
Example 6.1. Local Variables vs. Member Variables
class A { static int num = 0; // a member variable static void Go() { string str = "Fred"; // a local variable num++; } static void Main() { Go(); Console.WriteLine(num); } }
Just as with methods, member variables can be either static or non-static. Since static methods can use member variables only if they are marked static, in this chapter, we'll stick to static variables.
There are two important differences between member variables and local variables.
Local variables (like str in the example) lose their values when their method finishes. In the example, when Main calls Go(), and Go() finishes, the value of str is lost. In contrast, static member variables hold their values until the program ends.
Local variables cannot be used by code in other methods. For example, since str is a local variable defined in the Go() method, it can only be used by code in the Go() method. The compiler would issue an error if you try to display its value in the Main method like this:
Console.WriteLine(str); // illegal in Main
If you think about it a moment, this makes sense in light of the first point. Since str has no value outside of the Go() method, it wouldn't make sense to try to display (or change) its value in the Main() method.
In contrast, static member variables can be used by code in any method in the class. For this reason, you might think of them as "shared" variables.
Let me mention in passing that some C# API classes define static member variables that you can use in your program. For example, the Math class defines a static variable (actually a constant) named PI. To use static member variables defined in other classes, you must prefix the name of the variable with the name of the class and a dot, like this: Math.PI.
Since static member variables can be used by all methods in a class, they can be used to communicate information between different methods. As an example of this, let's add a static member variable to the GuessingGame class that we wrote earlier in this section. This variable will be used to keep track of how many games the user wins. We'll call the variable gamesWon and declare it with the statement "static int gamesWon;" In the playGame() routine, we add 1 to gamesWon if the user wins the game. At the end of the Main() routine, we print out the value of gamesWon. It would be impossible to do the same thing with a local variable, since we need access to the same variable from both methods.
When you declare a local variable in a method, you have to assign a value to that variable before you can do anything with it. Member variables, on the other hand are automatically initialized with a default value, if you do not provide one. For numeric variables, the default value is zero. For boolean variables, the default is false. And for char variables, it's the unprintable character that has Unicode code number zero. (For objects, such as strings, the default initial value is a special value called null, which we won't encounter officially until later.)
Since it is of type int, the static member variable gamesWon automatically gets assigned an initial value of zero. This happens to be the correct initial value for a variable that is being used as a counter. You can, of course, assign a different value to the variable.
Here's a revised version of GuessingGame.cs that includes the gamesWon variable. The changes from the above version are highlighted:
using System; class GuessingGame2 { static int gamesWon; // The number of games won by the user. static void Main() { Console.WriteLine("Let's play a game. I'll pick a number between"); Console.WriteLine("1 and 100, and you try to guess it."); string answer; do { PlayGame(); // call method to play one game Console.Write("Would you like to play again? (y/n) "); answer = Console.ReadLine(); } while (answer == "y"); Console.WriteLine(); Console.WriteLine("You won " + gamesWon + " game(s)."); Console.WriteLine("Thanks for playing. Goodbye."); } // Main() static void PlayGame() { int guessCount = 0; // Number of guesses the user has made. Random generator = new Random(); int computersNumber = generator.Next(1, 101); // The value assigned to computersNumber is a randomly // chosen integer between 1 and 100, inclusive. Console.WriteLine(); Console.Write("What is your first guess? "); bool done = false; while (done == false) { int usersGuess = Convert.ToInt32(Console.ReadLine()); // get the user's guess guessCount++; if (usersGuess == computersNumber) { Console.WriteLine("You got it in " + guessCount + " guesses! My number was " + computersNumber); gamesWon++; done = true; // the game is over; the user has won } else if (guessCount == 6) { Console.WriteLine("You didn't get the number in 6 guesses."); Console.WriteLine("You lose. My number was " + computersNumber); done = true; // the game is over; the user has lost } else { // If we get to this point, the game continues. // Tell the user if the guess was too high or too low. if (usersGuess < computersNumber) { Console.Write("That's too low. Try again: "); } else if (usersGuess > computersNumber) { Console.Write("That's too high. Try again: "); } } } // while Console.WriteLine(); } // playGame() } // end of class GuessingGame2
C# allows different methods to "reuse" variable names. Here's an example:
using System; class ScopeDemo1 { static void Main() { Console.WriteLine("Enter five numbers."); int num = 0; while (num < 5) { GetNumber(); num = num + 1; } } static void GetNumber() { Console.Write("Enter a number:"); int num = Convert.ToInt32(Console.ReadLine()); Console.WriteLine("You entered: " + num); } }
The num variable is defined twice -- once in Main(), and once in getNumber(). Each of these is treated as a separate, distinct variable, with its own memory space. Changes to num in getNumber() do not affect Main's num variable.
This behavior is helpful, because when you're focusing on a particular method that defines its own local variables, you don't have to worry about any potential conflicts with variables in other methods.
C# also allows you to define a local variable with the same name as a member variable. The following, for example, is legal:
class ScopeDemo2 { static int num; static void Main() { int num; // local variable num = 5; // affects the local num, not the member num } }
We have here two variables named num, each with their own separate memory space. This situation can easily get you into trouble. Note that, inside Main, the assignment statement affects the local variable num, not the member variable. In effect, the local variable definition "hides" the member variable. This situation usually arises unintentionally, and can result in hard-to-find bugs.
Tip | |
---|---|
If you have a program in which a method is supposed to be altering the value of a member variable, but the change seems to get lost when the method returns, this scenario is likely the cause of the trouble. |
You might wonder, given the restrictions on local variables, why we use local variables at all. We could define all variables in the program as static member variables, and then any method that needs to use a given variable could use it. This is easy to do and, in fact, many novice programmers (and unfortunately, some experienced programmers) take this approach.
There are important reasons not to make all variables static members. One has to do with the kinds of bugs that start to creep in when you do this. As an example, think of making guessCount a static member variable in GuessingGame2 by simply moving its definition outside the playGame method, and adding static to the front. Here's a fragment of what that section of the program would look like:
static int guessCount = 0; // Number of guesses the user has made. static void PlayGame() { int computersNumber = ...
Do you see the problem? If you're reading this online at a computer, I encourage you to take a moment to copy the program listing into an editor, make this one change, save, compile, and run. Play two games. Try to lose both of them. What happens?
The problem is that now that guessCount is a member variable, its value is not reset to 0 each time playGame is called. In the second game, its value starts at 6, gets incremented to 7 before the test for end of game, and just keeps going from there. Unless you guess the right number, you're caught in an infinite loop.
Another, much more subtle, bug occurs when two methods in a program inadvertently use the same member variable for different purposes. This is known as the "side effect" problem, and it is extremely difficult to find and fix. It is especially likely to occur with variables that have "generic" names (ex. num, count). To see this bug in action, take a look at the ScopeDemo1 program above. Remove both local variable definitions for num, and replace them with a single static int num member variable. Try out the program and see what happens.
The key reason we divide a program into separate methods is to help manage complexity. Methods help manage complexity because, when you use only local variables, you can look at each method as an independent block of code, and think about how it works without concerning yourself with the rest of the program. Instead of comprehending the entire program in your mind at once, you only have to comprehend each piece by itself. When methods start sharing variables, however, you can't focus on just one method at a time. You have to think about all the other methods that use that variable. If you're not selective about which variables are shared, you can wind up with a real mess on your hands.
So if using member variables has these drawbacks, can we get methods to communicate without using shared variables? Yes! Read on for other method communication techniques.