I've added a field called
Mixers into the
IMixes interface in Bix. This field needs intialized. No problem, I thought at first, I've definitely seen a property for initial value on fields.
So I set an initial value in the reference assembly like this. (The attributes are because the code is being generated by a compile-time tool and because the field should not be made available to development tools.)
[CompilerGenerated] [SpecialName] private ReadOnlyCollection<IMixer> mixers = new ReadOnlyCollection<IMixer>(new IMixer);
Good so far. Now to compile and view it in ILDasm. Here's the field declaration.
.field private specialname class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class [Bix.Mix]Bix.Mix.IMixer> mixers .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() ( 01 00 00 00 )
The first line above is the field declaration, and the second is the
SpecialNameAttribute is converted into the
specialname keyword on the field definition.) Nowhere in this simple CIL is the field's initial value set.
So where the field initialized?
The answer is that the field is initialized in the constructor. If you are familiar with C#, then you may be under the impression that every constructor calls a base type's constructor as its first step. From the logical standpoint of the C# language, this is true. From the standpoint of CIL, however, it's less true. As it turns out, code to initialize each instance field runs directly within each constructor, and it runs before the base constructor is called.
There are currently two constructors in the reference assembly's version of
EncapsulatesTestType: one parameterless and one for deserialization. The C# compiler generates the following code at the beginning of each.
IL_0000: ldarg.0 IL_0001: ldc.i4.0 IL_0002: newarr [Bix.Mix]Bix.Mix.IMixer IL_0007: newobj instance void class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class [Bix.Mix]Bix.Mix.IMixer>::.ctor(class [mscorlib]System.Collections.Generic.IList`1<!0>) IL_000c: stfld class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class [Bix.Mix]Bix.Mix.IMixer> Bix.Mixers.MixerTestTargets.EncapsulatesTestType::mixers IL_0011: ldarg.0 IL_0012: call instance void [mscorlib]System.Object::.ctor()
This code initializes the field to the dummy value which the reference assembly currently specifies, and then it calls the base constructor. If you think about it, it's sort of an AOP strategy to handle C#-like field initialization in CIL.
So now I have to worry about how to initialize the fields and keep them testable. I can easily push the code into all constructors, but I will need to worry about order. I could then sort the field initilizations in a deterministic way and compare. Unfortunately, that leaves the problem of separating generated fields from non-generated. Once that's solved, I will have to go a step further and separate fields generated by different behavior mixers.
I think that's getting a little too complex. I believe that I have an alternate solution in the works, but I will hold off on writing about it for now.