Skip to content

15.6.6.2 (and 15.13, 16.4.11) Type initialisation, as reported by CLI and C# #73

Open
@RexJaeschke

Description

@RexJaeschke

Raised by Nigel in October 2006

"At the April meetings in Geneva I had action items from both TG2 & TG3 (Jim Miller shared my TG3
one) to report back on what the intended story on type initialisation is. The aim was to try to resolve
the issue within four weeks after themeeting so any change required could be made to the Standard.
We didn't get a resolution in time.

I did however investigate the situation. The CLI & C# differ is what they state, and reality is different
again - though MS & Mono sometimes differ it is not in substance. We put a note in the forthcoming
Annotated C# Standard, and I forgot to report back to TG2 & TG3 once the deadline has passed...
So my apologies for forgetting, here it is what I found for the record. Thanks in particular to Jan Kotas for feedback. If we ever produce another edition we can consider what to do with these
discrepancies.

Below is (a) what the CLI spec says, (b) what the C# spec says, and (c) what is actually implemented:

(a) CLI:

The CLI currently states:

  1. If marked BeforeFieldInit then the type's initialiser method is executed at, or sometime
    before, first access to any static field defined for that type.
  2. If not marked BeforeFieldInit then that type's initialiser method is executed at (i.e., is
    triggered by):
  • first access to any static field of that type, or
  • first invocation of any static method of that type

or

  • first invocation of any constructor for that type.
  1. Execution of any type's initialiser method will not trigger automatic execution of any initializer
    methods defined by its base type, nor of any interfaces that the type implements
    For reference types, a constructor has to be called to create a non-null instance. Thus, for
    reference types, the .cctor will be called before instance fields can be accessed and methods
    can be called on non-null instances. For value types, an ""all-zero"" instance can be created
    without a constructor (but only this value can be created without a constructor). Thus for value
    types, the .cctor is only guaranteed to be called for instances of the value type that are not ""allzero"".
    [Note: This changes the semantics slightly in the reference class case from the first
    edition of this standard, in that the .cctor might not be called before an instance method is
    invoked if the 'this' argument is null. The added performance of avoiding class constructors
    warrants this change. end note]
    Which can be summarised as:

BFI then at or before first static field access
!BFI at first static field access, static method call or .ctor call
never on instance field access or instance method call (for structs)

(b) C#:
C# currently says:

17.11:
The execution of a static constructor is triggered by the first of the following events to occur
within an application domain:

  • An instance of the class is created.
  • Any of the static members of the class are referenced.

17.4.5.1:

If a static constructor (§17.11) exists in the class, execution of the static field initialisers
occurs immediately prior to executing that static constructor. Otherwise, the static field
initialisers are executed at an implementation-dependent time prior to the first use of a
static field of that class.

18.3.10:

Static constructors for structs follow most of the same rules as for classes. The execution of
a static constructor for a struct is triggered by the first of the following events to occur within
an application domain:

  • An instance member of the struct is referenced.
  • A static member of the struct is referenced.
  • An explicitly declared constructor of the struct is called.

[Note: The creation of default values (§18.3.4) of struct types does not trigger the static
constructor. (An example of this is the initial value of elements in an array.) end note]

Which can be summarised as:

No static constructor but fields inits => BFI
Class and !BFI at first static field access, static method call or .ctor call
Struct and !BFI at first field (static or instance) access, first method (static or instance) call, or
first .ctor call

(c) What is actually implemented:
(i) CLR & Mono

First Mono C# marks all structs as BFI regardless of whether there is a static constructor or
not. VS 2005 C# plays more freely with BFI timing, often for reasons that are not immediately
obvious. However if at is always read as at or before then there are no substantive
differences between the two.
BFI appears to be interpreted as init may trigger before it would for !BFI but it will trigger (it it
hasn't already) at the places !BFI will. I.e. BFI can still trigger even if there is no static field
access at all.

For classes apart from BFI matches CLI/C# spec.
For structs type init is triggered at (or before) first static field access, first static/instance
method call, or first .ctor call.

This is more than the CLI allows as it triggers on instance method access, but less than C#
requires as it doesn't trigger on instance field access. Also the fact the BFI triggers without
static field access also differs from CLI. So all three are different.

(ii) CLR NGEN
If BFI appears to require field access to trigger.
For classes appears to match CLI/C# spec.
For structs appears to match CLI spec.
So I think NGEN is matching the CLI spec.
No consensus was achieved and what the CLI or C# should be saying/doing, mainly due to the fact
we couldn't do anything about it anymore..."

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: bugThe Standard does not describe the language as intended or implemented

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions