The HelloWorld example in Chapter 1 first presented the keyword static; however, it did not define it fully. This section defines the static keyword fully.
To begin, consider an example. Assume that the employee Id value needs to be unique for each employee. One way to accomplish this is to store a counter to track each employee ID. If the value is stored as an instance field, however, every time you instantiate an object, a new NextId field will be created such that every instance of the Employee object would consume memory for that field. The biggest problem is that each time an Employee object instantiated, the NextId value on all of the previously instantiated Employee objects would need to be updated with the next ID value. What you need is a single field that all Employee object instances share.
To define data that is available across multiple instances, you use the static keyword, as demonstrated in Listing 5.22.
Listing 5.22. Declaring a Static Field
In this example, the NextId field declaration includes the static modifier and therefore is called a static field. Unlike Id, a single storage location for NextId is shared across all instances of Employee. Inside the Employee constructor, you assign the new Employee object's Id the value of NextId immediately before incrementing it. When another Employee class is created, NextId will be incremented and the new Employee object's Id field will hold a different value.
Just as instance fields (nonstatic fields) can be initialized at declaration time, so can static fields, as demonstrated in Listing 5.23.
Listing 5.23. Assigning a Static Field at Declaration
If no initialization for a static field is provided, the static field will automatically be assigned its default value (0, null, false, and so on), and it will be possible to access the static field even if it has never been explicitly assigned.
Nonstatic fields, or instance fields, have a new value for each object to which they belong. In contrast, static fields don't belong to the instance, but rather, to the class itself. As a result, you access a static field from outside a class via the class name. Consider the new Program class shown in Listing 5.24.
Listing 5.24. Accessing a Static Field
Output 5.4 shows the results of Listing 5.24.
To set and retrieve the initial value of the NextId static field, you use the class name, Employee, not a variable name. The only time you can eliminate the class name is from within code that appears within the class itself. In other words, the Employee(...) constructor did not need to use Employee.NextId because the code appeared within the context of the Employee class itself, and therefore, the context was already understood from the scope.
Even though you refer to static fields slightly differently than instance fields, it is not possible to define a static and an instance field with the same name in the same class. The possibility of mistakenly referring to the wrong field is high, and therefore, the C# designers decided to prevent such code.
Just like static fields, you access static methods directly off the class name (Console.ReadLine(), for example). Furthermore, it is not necessary to have an instance in order to access the method.
Because static methods are not referenced through a particular instance, the this keyword is invalid inside a static method. In fact, it is not possible to access either an instance field or an instance method directly from within a static method without a reference to the particular instance to which the field or method belongs.
In addition to static fields and methods, C# also supports static constructors. Static constructors are provided as a means to initialize a class (not the class instance). Static constructors are not called explicitly; instead, the runtime calls static constructors automatically upon first access to the class, whether via calling a regular constructor or accessing a static method or field on the class. You use static constructors to initialize the static data within the class to a particular value, particularly when the initial value involves more complexity than a simple assignment at declaration time. Consider Listing 5.25.
Listing 5.25. Declaring a Static Constructor
Listing 5.25 assigns the initial value of NextId to be a random integer between 100 and 1,000. Because the initial value involves a method call, the NextId initialization code appears within a static constructor and not as part of the declaration.
If assignment of NextId occurs within both the static constructor and the declaration, it is not obvious what the value will be when initialization concludes. The C# compiler generates CIL in which the declaration assignment is moved to be the first statement within the static constructor. Therefore, NextId will contain the value returned by randomGenerator.Next(101, 999) instead of a value assigned during NextId's declaration. Assignments within the static constructor, therefore, will take precedence over assignments that occur as part of the field declaration, as was the case with instance fields. Note that there is no support for defining a static finalizer.
Some classes do not contain any instance fields. Consider, for example, a Math class that has functions corresponding to the mathematical operations Max() and Min(), as shown in Listing 5.26.
Listing 5.26. Declaring a Static Class
This class does not have any instance fields (or methods), and therefore, creation of such a class would be pointless. Because of this, the class is decorated with the static keyword. The static keyword on a class provides two facilities. First, it prevents a programmer from writing code that instantiates the SimpleMath class. Second, it prevents the declaration of any instance fields or methods within the class. Since the class cannot be instantiated, instance members would be pointless.
C# 1.0 did not support static class declaration like this. Instead, programmers had to declare a private constructor. The private constructor prevented developers from ever instantiating an instance of the class outside of the class scope. Listing 5.27 shows the same Math class using a private constructor.
Listing 5.27. Declaring a Private Constructor
The effect of using a private constructor in Listing 5.27 is very similar to the static class used in Listing 5.26, except that it is still possible to instantiate the class in Listing 5.27 from inside the class implementation. In contrast, Listing 5.26 prevents instantiation from anywhere, including from inside the class itself. Another difference between declaring a static class and using a private constructor is that instance members are allowed on a class with private constructors, but the C# 2.0 compiler will disallow any instance members on a static class.
One more distinguishing characteristic of the static class is that the C# compiler automatically marks it as sealed. This keyword designates the class as inextensible; in other words, no class can be derived from it.