Static Members
The HelloWorld example in Chapter 1 briefly touched on the keyword static. This section defines the static keyword more fully.
Let’s 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 will consume memory for that field. The biggest problem is that each time an Employee object is instantiated, the NextId value on all of the previously instantiated Employee objects needs to be updated with the next ID value. In this case, what you need is a single field that all Employee object instances share.
Static Fields
To define data that is available across multiple instances, you use the static keyword, as demonstrated in Listing 5.34.
LISTING 5.34: Declaring a Static Field
class Employee
{
public Employee(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
Id = NextId;
NextId++;
}
// ...
public static int NextId;
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; } = "Not Enough";
// ...
}
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 the Id. 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.35.
LISTING 5.35: Assigning a Static Field at Declaration
class Employee
{
// ...
public static int NextId = 42;
// ...
}
Unlike with instance fields, 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)—the equivalent of default(T), where T is the name of the type. As a result, it will be possible to access the static field even if it has never been explicitly assigned in the C# code.
Nonstatic fields, or instance fields, provide a new storage location 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.36 (using the Employee class from Listing 5.34).
LISTING 5.36: Accessing a Static Field
using System;
class Program
{
static void Main()
{
Employee.NextId = 1000000;
Employee employee1 = new Employee(
"Inigo", "Montoya");
Employee employee2 = new Employee(
"Princess", "Buttercup");
Console.WriteLine(
"{0} {1} ({2})",
employee1.FirstName,
employee1.LastName,
employee1.Id);
Console.WriteLine(
"{0} {1} ({2})",
employee2.FirstName,
employee2.LastName,
employee2.Id);
Console.WriteLine(
$"NextId = { Employee.NextId }");
}
// ...
}
Output 5.9 shows the results of Listing 5.36.
OUTPUT 5.9
Inigo Montoya (1000000)
Princess Buttercup (1000001)
NextId = 1000002
To set and retrieve the initial value of the NextId static field, you use the class name, Employee, rather than a reference to an instance of the type. The only place you can omit the class name is within the class itself (or a derived class). 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. The scope of a variable is the program text in which the variable can be referred to by its unqualified name; the scope of a static field is the text of the class (and any derived classes).
Even though you refer to static fields slightly differently than you refer to instance fields, it is not possible to define a static field and an instance field with the same name in the same class. The possibility of mistakenly referring to the wrong field is high, so the C# designers decided to prevent such code. Overlap in names, therefore, introduces conflict within the declaration space.
Static Methods
Just like static fields, you access static methods directly off the class name—for example, as Console.ReadLine(). Furthermore, it is not necessary to have an instance to access the method.
Listing 5.37 provides another example of both declaring and calling a static method.
LISTING 5.37: Defining a Static Method on DirectoryInfo
public static class DirectoryInfoExtension
{
public static void CopyTo(
DirectoryInfo sourceDirectory, string target,
SearchOption option, string searchPattern)
{
if (target[target.Length - 1] !=
Path.DirectorySeparatorChar)
{
target += Path.DirectorySeparatorChar;
}
if (!Directory.Exists(target))
{
Directory.CreateDirectory(target);
}
for (int i = 0; i < searchPattern.Length; i++)
{
foreach (string file in
Directory.GetFiles(
sourceDirectory.FullName, searchPattern))
{
File.Copy(file,
target + Path.GetFileName(file), true);
}
}
//Copy subdirectories (recursively)
if (option == SearchOption.AllDirectories)
{
foreach(string element in
Directory.GetDirectories(
sourceDirectory.FullName))
{
Copy(element,
target + Path.GetFileName(element),
searchPattern);
}
}
}
}
__________________________________________________________________________________
// ...
DirectoryInfo directory = new DirectoryInfo(".\\Source");
directory.MoveTo(".\\Root");
DirectoryInfoExtension.CopyTo(
directory, ".\\Target",
SearchOption.AllDirectories, "*");
// ...
In Listing 5.37, the DirectoryInfoExtension.Copy() method takes a DirectoryInfo object and copies the underlying directory structure to a new location.
Because static methods are not referenced through a particular instance, the this keyword is invalid inside a static method. In addition, 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. (Note that Main() is another example of a static method.)
One might have expected this method on the System.IO.Directory class or as an instance method on System.IO.DirectoryInfo. Since neither exists, Listing 5.37 defines such a method on an entirely new class. In the section “Extension Methods” later in this chapter, we show how to make it appear as an instance method on DirectoryInfo.
Static Constructors
In addition to static fields and methods, C# supports static constructors. Static constructors are provided as a means to initialize the class itself, rather than the instances of a class. Such constructors are not called explicitly; instead, the runtime calls static constructors automatically upon first access to the class, whether by calling a regular constructor or by accessing a static method or field on the class. Because the static constructor cannot be called explicitly, no parameters are allowed on static constructors.
You use static constructors to initialize the static data within the class to a particular value, primarily when the initial value involves more complexity than a simple assignment at declaration time. Consider Listing 5.38.
LISTING 5.38: Declaring a Static Constructor
class Employee
{
static Employee()
{
Random randomGenerator = new Random();
NextId = randomGenerator.Next(101, 999);
}
// ...
public static int NextId = 42;
// ...
}
Listing 5.38 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.
Be careful not to throw an exception from a static constructor, as this will render the type unusable for the remainder of the application’s lifetime.6
Static Properties
Begin 2.0
You also can declare properties as static. For example, Listing 5.39 wraps the data for the next ID into a property.
LISTING 5.39: Declaring a Static Property
class Employee
{
// ...
public static int NextId
{
get
{
return _NextId;
}
private set
{
_NextId = value;
}
}
public static int _NextId = 42;
// ...
}
It is almost always better to use a static property rather than a public static field, because public static fields are callable from anywhere, whereas a static property offers at least some level of encapsulation.
Begin 6.0
In C# 6.0, the entire NextId implementation—including an inaccessible backing field—can be simplified down to an automatically implemented property with an initializer:
public static int NextId { get; private set; } = 42;
End 6.0
Static Classes
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.40.
LISTING 5.40: Declaring a Static Class
// Static class introduced in C# 2.0
public static class SimpleMath
{
// params allows the number of parameters to vary.
public static int Max(params int[] numbers)
{
// Check that there is at least one item in numbers.
if(numbers.Length == 0)
{
throw new ArgumentException(
"numbers cannot be empty", "numbers");
}
int result;
result = numbers[0];
foreach (int number in numbers)
{
if(number > result)
{
result = number;
}
}
return result;
}
// params allows the number of parameters to vary.
public static int Min(params int[] numbers)
{
// Check that there is at least one item in numbers.
if(numbers.Length == 0)
{
throw new ArgumentException(
"numbers cannot be empty", "numbers");
}
int result;
result = numbers[0];
foreach (int number in numbers)
{
if(number < result)
{
result = number;
}
}
return result;
}
}
__________________________________________________________________________________
public class Program
{
public static void Main(string[] args)
{
int[] numbers = new int[args.Length];
for (int count = 0; count < args.Length; count++)
{
numbers[count] = args[count].Length;
}
Console.WriteLine(
$@"Longest argument length = {
SimpleMath.Max(numbers) }");
Console.WriteLine(
$@"Shortest argument length = {
SimpleMath.Min(numbers) }");
}
}
This class does not have any instance fields (or methods), so creation of such a class would be pointless. Consequently, 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. Because the class cannot be instantiated, instance members would be pointless. The Program class in prior listings is another good candidate for a static class because it too contains only static members.
End 2.0
One more distinguishing characteristic of the static class is that the C# compiler automatically marks it as abstract and sealed within the CIL. This designates the class as inextensible; in other words, no class can be derived from this class or even instantiate it.
Begin 6.0
Begin 3.0
In the previous chapter, we saw that the using static directive can be used with static classes such as SimpleMath. For example, adding a using static SimpleMath; declarative at the top of Listing 5.40 would allow you to invoke Max without the SimpleMath prefix:
Console.WriteLine(
$@"Longest argument length = { Max(numbers) }");
End 6.0