Constructors vs. Data Constants

Constructors vs. Data Constants

We’ve already taken a look at field mapping as a technique of field initialization, and I’ve listed the drawbacks and limitations of this technique. Field mapping has this distinct “unmanaged” scent about it, but the compilers routinely use it for field initialization nevertheless. Is there a way to get the fields initialized without mapping them? Yes, there is.

The common language runtime object model provides two special methods, the instance constructor (.ctor) and the class constructor (.cctor), a.k.a. the type initializer. We’re getting ahead of ourselves a bit here; methods in general and constructors in particular are discussed in Chapter 9, so we won’t concentrate on details here. For now, all we need to know about .ctor and .cctor is that .ctor is executed when a new instance of a type is created, and .cctor is executed after the type is loaded and before any one of the type members is accessed. Because class constructors are static and can deal with static members of the type only, we have a perfect setup for field initialization: .cctors take care of static fields, and .ctors take care of instance fields.

But how about global fields? The good news is that we can define a global .cctor. (Don’t try this in the second beta version of the common language runtime, if you can still find a copy; global class constructors were not allowed in this beta version.) Field initialization by constructors is vastly superior to field mapping, with none of its limitations, as described earlier in the section “Mapped Fields.” The catch? Unfortunately, initialization by constructors must be executed at run time, burning processor cycles, whereas mapped fields simply “are there” after the module has been loaded. The mapped fields don’t require additional operations for the initialization. Whether this price is worth the increased freedom and safety regarding field initialization depends on the concrete situation, but in general I think it is.

Let me illustrate the point by building an alternative enumerator. Because all the values of an enumerator are stored in literal fields, which are inaccessible from IL directly, the compilers replace references to these fields with the respective values at compile time. We can use a very simple enumerator as a model:

.class public enum sealed MagicNumber {    .field private specialname int32 value__    .field public static literal valuetype        MagicNumber MagicOne = int32(123)    .field public static literal valuetype         MagicNumber MagicTwo = int32(456)    .field public static literal valuetype        MagicNumber MagicThree = int32(789) }

Let’s suppose that our code uses the symbolic constants of an enumerator declared in a third-party assembly. We compile the code, and the symbolic constants are replaced with their values. Forget for a moment that we must have that third-party assembly available at compile time. But we will need to recompile the code every time the enumerator changes, and we have no control over the enumerator because it is defined outside our jurisdiction. In another scenario, when we declare an enumerator in one of our own modules, we must recompile all the modules that reference this enumerator once it is changed.

Let’s suppose also—for the sake of an argument—that we don’t like this situation, so we decide to devise our own enumerator:

.class public value sealed MagicNumber {    .field public int32 _value_ // Specialname value__ is                                 // reserved for enums    .field public static valuetype MagicNumber MagicOne at D_00    .field public static valuetype MagicNumber MagicTwo at D_04    .field public static valuetype MagicNumber MagicThree at D_08 } .data D_00 = int32(123) .data D_04 = int32(456) .data D_08 = int32(789)

This solution looks good, except in the platform-independence department. We conquered the recompilation problem and can at last address the symbolic constants by their symbols (names), through field access instructions. This approach presents two problems, though. First, the fields representing the symbolic constants can be written to. Second, it works fine with integers, but what if we need a string enumeration?

Let’s try again with a class constructor; refer to the sample MyEnums.il on the companion CD.

.class public value sealed MagicNumber {    .field private int32 _value_ // Specialname value__ is                                  // reserved for enums    .field public static initonly valuetype MagicNumber MagicOne    .field public static initonly valuetype MagicNumber MagicTwo    .field public static initonly valuetype MagicNumber MagicThree    .method public static specialname void .cctor()    {       ldsflda valuetype MagicNumber MagicNumber::MagicOne       ldc.i4 123       stfld int32 MagicNumber::_value_       ldsflda valuetype MagicNumber MagicNumber::MagicTwo       ldc.i4 456       stfld int32 MagicNumber::_value_       ldsflda valuetype MagicNumber MagicNumber::MagicThree       ldc.i4 789       stfld int32 MagicNumber::_value_       ret    }    .method public int32 ToBase()    {       ldarg.0 // Instance pointer       ldfld int32 MagicNumber::_value_       ret    } }

All the remaining problems seem to be solved. The initonly flag on the static fields protects them from being overwritten outside the class constructor. Embedding the numeric values of symbolic constants in the IL stream takes care of platform dependence. Because we are not mapping the fields, we are free to use any type as the underlying type of our enumerator. And, of course, declaring the _value_ field private protects it from having arbitrary values assigned to it.

Alas, there is a hidden problem with this solution: the initonly flag does not provide full protection against arbitrary field overwriting. In the first release of the runtime, the operations ldflda (ldsflda) and stfld (stsfld) on initonly fields are unverifiable outside the constructors. Unverifiable but not impossible, which means that if the verification procedures are disabled, the initonly fields can be overwritten in any method.



Inside Microsoft. NET IL Assembler
Inside Microsoft .NET IL Assembler
ISBN: 0735615470
EAN: 2147483647
Year: 2005
Pages: 147
Authors: SERGE LIDIN

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net