Type Initialization

Since static initonly fields are resolved when the code that uses the field is JIT-compiled, it is not necessary to specify the value of a static initonly field at build time. However, the value of a static initonly field must be determined before the JIT compiler resolves the field. To achieve this, .NET comes with a new construct called a type initializer. According to the CLI specification, a type initializer is called by the runtime "at or before first access of any static field of that type." (For details on the timing guarantees for type initialization, please refer to the documentation on the beforefieldinit modifier in the CLI specification.)

Type initializers are sometimes called static constructors because many .NET languages, including C++/CLI and C#, allow you to define a type initializer with a constructor-like syntax. The following code shows a static constructor that reads a value from a configuration file:

ref class TaxRates {

public:

static initonly float GermanVAT; static initonly float UKVAT;

static TaxRates() {

GermanVAT = float::Parse(ConfigurationManager::AppSettings["GermanVAT"]); UKVAT = float::Parse(ConfigurationManager::AppSettings["UKVAT"]);

When static constructors are implemented, exceptions must be considered, too. To discuss exceptions that occur during type initialization, I'll review the static constructor implemented previously. The expression ConfigurationManager::AppSettings["GermanVAT"] evaluates to nullptr when a configuration file like the one following is not found:

<configuration> <appSettings> <add key="GermanVAT" value="0.16"/> <add key="UKVAT" value="0.175"/> </appSettings> </configuration>

When float::Parse is called with nullptr as an argument, it throws a System::ArgumentNullException. Since the exception is not caught within the constructor, it will be handled by the caller—the runtime. In this case, the type is marked as unusable. The code that caused the JIT compilation will face a System::TypeInitializationException. All further attempts to use the type in any way will also cause a TypelnitializationException to be thrown. Since developers rarely expect such an exception when they instantiate a type or use its static members, you should not allow exceptions to be thrown from a type initializer. The following code shows how an exception can be avoided in the TaxRates type initializer:

static TaxRates() {

if (!float::TryParse(ConfigurationManager::AppSettings["GermanVAT"], GermanVAT)) GermanVAT = 0.16;

if (!float::TryParse(ConfigurationManager::AppSettings["UKVAT"], UKVAT)) UKVAT = 0.175;

If a static field or a static initonly field is initialized within the field's declaration, the C++/CLI compiler automatically emits a static constructor that initializes the field. The following class demonstrates this:

ref class TaxRates {

static float GetConfigValue(StringA key, float defaultValue) {

if (!float::TryParse(ConfigurationManager::AppSettings[key], value))

value = defaultValue; return value;

public:

static initonly float GermanVAT = GetConfigValue("GermanVAT", 0.16); static initonly float UKVAT = GetConfigValue("UKVAT", 0.175);

The CLR is implemented so that static constructors are called in a thread-safe way. For example, if one thread tries to access a static field of a managed type while another thread is currently executing the managed type's initializer, the first thread has to wait.

In rare cases, you may want to ensure that a type initializer is called even though you are not about to use a type directly. You can achieve this by calling

System::Runtime::CompilerServices::RuntimeHelpers::RunClassConstructor, passing the type info object for the type you want to preinitialize.

Was this article helpful?

0 0

Post a comment