As we've just seen, static methods are used to provide generic functionality that is independent of any particular object. For example, we've been using the syntax
Console.WriteLine(string expression);
throughout the book to display messages to the console. As it turns out, Console is a class predefined in the .NET Framework Class Library (in the System name-space), and WriteLine is a static method on that class. We therefore needn't ever instantiate a Console object to print messages to the screen; we simply call the WriteLine method on the Console class as a whole.
Another example of a predefined class that is comprised wholly of static methods and public static attributes is the Math class (also in the System namespace).
The Math class declares a variety of static methods to compute trigonometric, exponential, logarithmic, and power functions, to round numeric values, and to generate random numbers. We saw the use of one such method—Math.Sqrt()—in an example in Chapter 6:
squareRoot[i] = Math.Sqrt(i);
The mathematical constants e and π are declared as public static attributes of the Math class, named Math.E and Math.PI, respectively:
Console.WriteLine("The value of pi = " + Math.PI);
We informally refer to such classes as utility classes.
We can use this same technique to create our own custom utility classes. For example, suppose that we were going to have a frequent need to do temperature conversions from degrees Fahrenheit to degrees Centigrade and vice versa. We could invent a utility class as follows:
// A utility class to provide F=>C and C=>F conversions. public class Temperature { public static double FahrenheitToCentigrade(double tempF) { double tempC = (tempF - 32.0) * (5.0/9.0); return tempC; } public static double CentigradeToFahrenheit(double tempC) { double tempF = tempC * (9.0/5.0) + 32.0; return tempF; } }
Then, to use this class, we'd simply write client code as follows:
double temp1 = 212.0; // Boiling point on the Fahrenheit scale // Calling our own static method. double temp2 = Temperature.FahrenheitToCentigrade(temp); Console.WriteLine("" + temp + " degrees F = " + temp2 + "degrees C");
This would give us the following output:
212.0 degrees F = 100.0 degrees C
We might even wish to include some commonly used constants—say, the boiling and freezing points of water in both F and C terms—as public static attributes in our utility class:
// A utility class to provide F=>C and C=>F conversions. public class Temperature { // We've added some public static attributes. public static double FahrenheitFreezing = 32.0; public static double CentigradeFreezing = 0.0; public static double FahrenheitBoiling = 212.0; public static double CentigradeBoiling = 100.0; public static double FahrenheitToCentigrade(double tempF) { double tempC = (tempF – 32.0) * (5.0/9.0); return tempC; } public static double CentigradeToFahrenheit(double tempC) { double tempF = tempC * (9.0/5.0) + 32.0; return tempF; } }
We could then take advantage of these constants in our client code, as well:
double soupTemperature; // The value of soupTemperature is established ... details omitted. if (soupTemperature >= Temperature.FahrenheitBoiling) { ... }
There is only one minor problem: we want these "constant" values to truly be constants, but as we've declared them previously—as public (static) attributes—there is nothing to prevent client code from altering their values:
Temperature.FahrenheitBoiling = 98.6; // Whoops!
Fortunately, we can take advantage of a special type of variable known as a constant to remedy this problem.
A constant is a variable whose value can't be changed once it has been given an initial value. We declare a constant with the const keyword, as follows:
public const double FahrenheitFreezing = 32.0;
Constants are implicitly static, and so the static keyword shouldn't be used in declaring them; if we try to do so:
// The following line won't compile. public static const double FahrenheitFreezing = 32.0;
the compiler will generate the following error:
error CS0504: 'Temperature.FahrenheitFreezing' cannot be marked static
Constants must be given a value when they are declared; that is, we can't declare a const in one part of a program and assign it a value somewhere else. If we try to declare an uninitialized const:
public const double FahrenheitFreezing;
the compiler will generate the following error:
error CS0145: a const field requires a value to be provided
The convention when naming constants is to use the Pascal capitalization style.
Let's retrofit our Temperature class with constant attributes:
// A utility class to provide F=>C and C=>F conversions. public class Temperature { // We've added the const keyword to these declarations. public const double FahrenheitFreezing = 32.0; public const double CentigradeFreezing = 0.0; public const double FahrenheitBoiling = 212.0; public const double CentigradeBoiling = 100.0; public static double FahrenheitToCentigrade(double tempF) { double tempC = (tempF - 32.0) * (5.0/9.0); return tempC; } public static double CentigradeToFahrenheit(double tempC) { double tempF = tempC * (9.0/5.0) + 32.0; return tempF; } }
Now, if we attempt to alter the value of one of these truly constant constants from client code:
Temperature.FahrenheitBoiling = 98.6; // This won't compile!
we'd get the following (admittedly somewhat cryptic) compilation error:
error CS0219: The left-hand side of an assignment must be a variable, property, or indexer
Of course, even within our Temperature class, this same prohibition exists: after the first assignment of a value to any const variable, that value is unchangeable.
Other facts about consts:
The initial value assigned to a const must be an expression that is computable at compile time:
public class MyUtilityClass { // This will compile ... public const int ImportantConstant = 123 + 456; // This will NOT ... public const double AnotherConstant = Math.Sqrt(2.0);
In the preceding snippet, the second const declaration won't compile, because Math.Sqrt is a method and hence can only be invoked at run time.
The type of a const can only be one of the predefined numerical types (char, int, double, byte, etc.) or a string.
We may declare consts locally to a method, as well:
public class SomeClass { // Details omitted. public void SomeMethod() { int x; const int y = 7; // etc. } }