Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
RexJaeschke opened this issue Apr 1, 2014 · 5 comments
Labels
type: bug The Standard does not describe the language as intended or implemented

Comments

@RexJaeschke
Copy link
Contributor

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..."

@jskeet
Copy link
Contributor

jskeet commented Apr 22, 2014

Mads to clarify current implementation in Microsoft and then assign to someone from Xamarin to check the Mono behaviour.

@jskeet jskeet changed the title Type initialisation, as reported by CLI and C# 16.6.6.2 (and 16.13, 17.4.11) Type initialisation, as reported by CLI and C# May 20, 2014
@jskeet jskeet changed the title 16.6.6.2 (and 16.13, 17.4.11) Type initialisation, as reported by CLI and C# 15.6.6.2 (and 15.13, 16.4.11) Type initialisation, as reported by CLI and C# Mar 26, 2018
@jskeet
Copy link
Contributor

jskeet commented Mar 26, 2018

All comments prior to this one were before we removed section 7. I've updated the title only.

@Nigel-Ecma
Copy link
Contributor

This is a (slightly, converted from email HTML to MD using the first online convertor I found) better formatted version of the first post above.

Section numbers have also been updated to match v5 and one change made to Standard since this was written has been struck out so what it was and now is can be seen.


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 the meeting 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:

3. 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.

4. 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.

5. 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 "all-zero". [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:

15.12:

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.

15.5.6.2:

If a static constructor (§15.12) 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.

16.4.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. (This bullet removed in v5)

  • A static member of the struct is referenced.

  • An explicitly declared constructor of the struct is called.

[Note: The creation of default values (§16.4.5) 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...

@jskeet
Copy link
Contributor

jskeet commented Dec 15, 2020

And now of course we have more flavours of .NET to consider.

I suspect we may just want to close this - but if not, I'd want to at least reinterpret the above for clarity... I'm getting very confused by it. It would be good to have some concrete examples where C# and the CLI spec differ (e.g. "C# would permit output of X; CLI would prohibit it").

@jskeet jskeet transferred this issue from another repository Dec 15, 2020
@jskeet jskeet added the type: bug The Standard does not describe the language as intended or implemented label Dec 15, 2020
@jskeet
Copy link
Contributor

jskeet commented Dec 15, 2020

(Marking as "bug" speculatively. It's not entirely clear whether it will be a bug or not.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug The Standard does not describe the language as intended or implemented
Projects
None yet
Development

No branches or pull requests

4 participants