Constants


A constant serves a very basic, yet extremely useful purpose. The constant is a symbolic representation of an unchanging value. Rather than hard-coding numeric and string values, they should be assigned to constants instead. A numeric value scattered throughout code without explanation is often referred to as a magic number. These magic numbers can make maintenance a tedious task and are prone to introduce bugs into code.

Java Note:

Java does not have a constant type. Rather, Java achieves a similar function through the use of public variables declared as static final.

To illustrate, we're going to create a simple guessing game (magicnumbers.cs). We have chosen to place the code for presentation in the main class, MagicNumbers. However, to encapsulate the rules of the game, we have placed the game logic in a separate class, GuessTheNumber:

    using System;    public class GuessTheNumber    {      public string tryGuess(int guess)      {        Random generator = new Random();        int randomNumber=generator.Next(1,10);        if (guess == randomNumber)          return "You guessed it!";        else          return "Sorry, the number was " + randomNumber.ToString();      }    }    class MagicNumbers    {      [STAThread]      static void Main(string[] args)      {        Console.WriteLine("We're going to play a guessing game!");        Console.WriteLine("Please enter a number between 1 and 10:");        try        {          int myGuess=int.Parse(Console.ReadLine());          if ((myGuess > 10) || (myGuess <1))            Console.WriteLine("Value must be between 1 and 10");          else          {            GuessTheNumber gtn=new GuessTheNumber();            Console.WriteLine(gtn.tryGuess(myGuess));          }        }        catch        {          Console.WriteLine("You need to enter a number!");        }      }    } 

While the example is a bit contrived, it does illustrate how magic numbers can cause problems. In our example, we need to store the minimum and maximum guess ranges in two classes. The problem is further compounded when we look at just the MagicNumbers class. Here, we reference the values multiple times for display to the user.

If, for any reason, we need to change the rules of our game, perhaps to make it a bit harder and let someone guess a number between one and twenty, then we would need to find all instances of our magic numbers and replace them. Of course, this is a rather easy task with our example, but just imagine a multi-thousand-line program with the same challenges as our example. The chance of bugs appearing in previously working code because a number was missed would be very high.

Now, let's fix that example up and show the power of constants. Again, the goal is to provide symbolic constants for values that will not change at runtime. It is good practice to group related constants together. The minimum and maximum number on our guessing game look to be a good place to use constants. The following code can be found in magicnumbers_revised.cs:

    using System;    public class GuessTheNumber    {      public const int LOW=1;      public const int HIGH=10;      public string tryGuess(int guess)      {        Random generator = new Random();        int randomNumber=generator.Next(LOW, HIGH);        if (guess == randomNumber)            return "You guessed it!";        else            return "Sorry, the number was " + randomNumber.ToString();      }    }    class MagicNumbers    {      [STAThread]      static void Main(string[] args)      {        GuessTheNumber numberGame=new GuessTheNumber();        Console.WriteLine("We're going to play a guessing game!");        Console.WriteLine("Please enter a number between "                          + GuessTheNumber.LOW + " and "                          + GuessTheNumber.HIGH + ":");        try        {          int myGuess=int.Parse(Console.ReadLine());          if ((myGuess > GuessTheNumber.HIGH) ||              (myGuess < GuessTheNumber.LOW))               Console.WriteLine("Value must be between "                                 + GuessTheNumber.LOW + " and "                                 + GuessTheNumber.HIGH);          else          {            Console.WriteLine(numberGame.tryGuess(myGuess));          }        }        catch        {          Console.WriteLine("You need to enter a number!");        }      }    } 

One important thing to note is that we are actually referencing the class GuessTheNumber to access the constant. Constants are not defined for each instance of the class. Rather, they are only defined once. We've also defined our constants as public. If a constant is only needed within the current class, then it is perfectly fine to declare the constant as private instead. We will cover other access modifiers later in the chapter.

The actual value of a constant is determined at compile time. When the code is compiled from C# into MSIL, the compiler will insert the constant's value in the resulting executable's metadata. One of the strong points of using constants, aside from tidying up code, is that inserting the constant value directly into the metadata does not require memory allocation. However, since the values are inserted straight into MSIL, constants may only be a primitive type.

Let's take a look at the resulting MSIL for the Main()method of the MagicNumbers class. After compiling the code, we will use the ildasm.exe utility included in the .NET Framework. This utility allows us to view the MSIL and metadata associated with .NET assemblies. The following code from the Main() method:

    class magicnumbers    {      [STAThread]      static void Main(string[] args)      {        GuessTheNumber numberGame=new GuessTheNumber();        Console.WriteLine("We're going to play a guessing game!");        Console.WriteLine("Please enter a number between "          + GuessTheNumber.LOW" + " and " + GuessTheNumber.HIGH + ":"); 

will be compiled into MSIL as shown in the screenshot below. Notice that the associated values for the HIGH and LOW constants of the GuessTheNumber class are embedded into the MSIL of the magicnumber class:

click to expand

The fact that .NET embeds constants into the executable code presents a potentially serious downfall. Suppose the constant is defined within another assembly, a DLL for example, and our code was compiled against that DLL. At compilation time, the value of the constants are read from the DLL and placed directly into our code.

If the constants in the DLL change, then our code would need to be recompiled as well. Otherwise, our code is unaware of any changes in the other DLL. Both our code, and the DLL it is relying on, will continue to function, with one serious problem; the constants are no longer consistent.

Assume we are coding a class that uses a connection string to a database. Instead of typing the same string throughout the code, you code the connection string as a constant and use the constant instead. Without thinking, you have locked the code into that particular connection string. If any portion of that connection string changes (database name, IP address, protocol, data provider, and so on), you will be in perhaps a difficult position to recompile and redeploy the code just to fix the connection string.

Not-so Constants

Remember, a constant's value is substituted in at compile time. As such, it is restricted to primitive types. Additionally, because of this compile-time substitution, if a constant changes, then every other class that relies on that constant must also be recompiled.

However, there is another approach to achieve similar functionality to a constant. This method is to declare a variable as a static read-only variable. Where constants are resolved at compile-time, static read-only variables are resolved at run time. There are two main advantages of a static read-only variable:

  • Unlike constants, they do not require dependent classes to be recompiled when they change.

  • A static read-only variable can be assigned any type. It is not restricted to primitive types.

There is also a disadvantage to using static read-only variables, at least when compared to constants. A constant is directly substituted into the MSIL. As we demonstrated above, this is true even when a constant is referenced between classes. In contrast, a static read-only variable must maintain a link to the corresponding class.

In terms of syntax, creating and using static, read-only variables is not much different from defining and accessing a constant:

    public class GuessTheNumber    {      public static readonly int LOW=1;      public static readonly int HIGH=10;         . . . . .        //code omitted for brevity         ... . .    } 

However, the corresponding MSIL is much different. Now, rather than simply substituting in a constant value, we have to establish a reference to the class containing the read-only variable:

click to expand

A third option, which we will not review in detail here, is to use application configuration files. Application configuration is extremely flexible within the .NET Framework and proves to be an excellent mechanism for application-specific information. For more on application configuration files, check out the MSDN.

Configuration files are ideal for storing deployment-oriented information. For example, database connection information should be stored in a configuration file. Of course, with its ability to be resolved at run time, a static read-only variable could be used to access and store values from a configuration file.

Let's summarize what we've learned in this section to help you understand when to use constants, static read-only fields, or application configuration files:

  • As a rule, only constants that do not change should be exposed to external classes. Of course, a constant is still a very useful mechanism when only used inside a class to minimize magic numbers. In this scenario, even frequently changing values will not be an issue, as the changes will be applied with each recompile.

  • If a variable must be determined at run time, or must contain a reference to a complex type, as opposed to the primitive types that a constant is limited to, then a static read-only variable is the way to go.

  • Lastly, a configuration file should be used to store information related to the setup, configuration, and deployment of an application. Constants serve a very useful purpose in .NET, but it is crucial to understand where constants are best applied and where another approach is better suited to solve a given problem in any programming language.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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