- Item 11: Understand .NET Resource Management
- Item 12: Prefer Member Initializers to Assignment Statements
- Item 13: Use Proper Initialization for Static Class Members
- Item 14: Minimize Duplicate Initialization Logic
- Item 15: Avoid Creating Unnecessary Objects
- Item 16: Never Call Virtual Functions in Constructors
- Item 17: Implement the Standard Dispose Pattern
Item 13: Use Proper Initialization for Static Class Members
You know that you should initialize static member variables in a type before you create any instances of that type. C# lets you use static initializers and a static constructor for this purpose. A static constructor is a special function that executes before any other methods, variables, or properties defined in that class are accessed for the first time. You use this function to initialize static variables, enforce the singleton pattern, or perform any other necessary work before a class is usable. You should not use your instance constructors, some special private function, or any other idiom to initialize static variables. For static fields that require complex or expensive initialization, consider using Lazy<T> to execute the initialization when a field is first accessed.
As with instance initialization, you can use the initializer syntax as an alternative to the static constructor. If you simply need to allocate a static member, use the initializer syntax. When you have more complicated logic to initialize static member variables, create a static constructor.
Implementing the singleton pattern in C# is the most frequent use of a static constructor. Make your instance constructor private, and add an initializer:
public class MySingleton { private static readonly MySingleton theOneAndOnly = new MySingleton(); public static MySingleton TheOnly { get { return theOneAndOnly; } } private MySingleton() { } // remainder elided }
The singleton pattern can just as easily be written this way, in case you have more complicated logic to initialize the singleton:
public class MySingleton2 { private static readonly MySingleton2 theOneAndOnly; static MySingleton2() { theOneAndOnly = new MySingleton2(); } public static MySingleton2 TheOnly { get { return theOneAndOnly; } } private MySingleton2() { } // remainder elided }
Like instance initializers, the static initializers are executed before any static constructors are called. And, yes, your static initializers may execute before the base class’s static constructor.
The CLR calls your static constructor automatically before your type is first accessed in an application space (an AppDomain). You can define only one static constructor, and it must not take any arguments. Because static constructors are called by the CLR, you must be careful about exceptions generated in them. If you let an exception escape a static constructor, the CLR will terminate your program by throwing a TypeInitializationException. The situation where the caller catches the exception is even more insidious. Code that tries to create the type will fail until that AppDomain is unloaded. The CLR could not initialize the type by executing the static constructor. It won’t try again, and yet the type did not get initialized correctly. An object of that type (or any type derived from it) would not be well defined. Therefore, it is not allowed.
Exceptions are the most common reason to use the static constructor instead of static initializers. If you use static initializers, you cannot catch the exceptions yourself. With a static constructor, you can (see Item 47):
static MySingleton2() { try { theOneAndOnly = new MySingleton2(); } catch { // Attempt recovery here. } }
Static initializers and static constructors provide the cleanest, clearest way to initialize static members of your class. They are easy to read and easy to get correct. They were added to the language to specifically address the difficulties involved with initializing static members in other languages.