-
Notifications
You must be signed in to change notification settings - Fork 87
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
Add support for module initializers #992
base: draft-v9
Are you sure you want to change the base?
Add support for module initializers #992
Conversation
- A *return_type* of `void`. | ||
- No *type_parameter_list*. | ||
- Not be declared inside a *class_declaration* having a *type_parameter_list*. | ||
- Be accessible from the containing module (that is, have an access modifier `internal` or `public`). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does that allow protected internal
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it does. The rules were chosen to describe "callable from <Module>..cctor
while following .NET accessibility rules." You can simulate it by asking whether the method would be accessible from a regular C# or .NET IL from a regular top-level class's static constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, protected internal
is not an access modifier; it is a sequence of two access modifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But how about:
public class C {
private class D {
[System.Runtime.CompilerServices.ModuleInitializer]
public static void M() {
}
}
}
The declaration of C.D.M() has the public
access modifier but SharpLab.io tells me "error CS8814: Module initializer method 'M' must be accessible at the module level". So the wording in this PR does not seem quite accurate.
The wording might have to be changed again in the future when C# 11 file-local types are specified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a really good point.
@@ -860,6 +861,22 @@ For invocations that occur within field or event initializers, the member name u | |||
|
|||
For invocations that occur within declarations of instance constructors, static constructors, finalizers and operators the member name used is implementation-dependent. | |||
|
|||
### §module-init-attr The ModuleInitializer attribute | |||
|
|||
The attribute `ModuleInitializer` is used to mark a method as a ***module initializer***. Such a method is called during initialization of the containing module. A module may have multiple initializers, which are called in an implementation-defined order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how much you want to repeat, but from https://github.com/dotnet/runtime/blob/main/docs/design/specs/Ecma-335-Augments.md#module-initializer:
Module Initializer
All modules may have a module initializer. A module initializer is defined as the type initializer (§ II.10.5.3) of the
<Module>
type (§ II.10.8).There are no limitations on what code is permitted in a module initializer. Module initializers are permitted to run and call both managed and unmanaged code.
Module Initialization Guarantees
In addition to the guarantees that apply to all type initializers, the CLI shall provide the following guarantees for module initializers:
A module initializer is executed at, or sometime before, first access to any static field or first invocation of any method defined in the module.
A module initializer shall run exactly once for any given module unless explicitly called by user code.
No method other than those called directly or indirectly from the module initializer will be able to access the types, methods, or data in a module before its initializer completes execution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This part specifically is interesting:
A module initializer is executed at, or sometime before, first access to any static field or first invocation of any method defined in the module.
Because there is no guarantee that the runtime will execute the initializer before using reflection to access types in the module, and in fact it does not, and this means that libraries can't use module initializers to hook up to AppDomain.AssemblyResolve in time to load assemblies required by such reflection. (Examples of wanting to do this is when assemblies are stored in nonstandard locations such as embedded resources or other places on disk, or when you need to handle assembly versioning differences and you can't use binding redirects. What all of these have in common are scenarios where a .NET library is a plugin to a third-party application.)
This is a great point. It seems like any combo of compiler and .NET runtime implementation would imply calling the containing class's public class Outer
{
[ModuleInitializer]
public static void Test()
{
Console.WriteLine("In module initializer"); // This runs second
}
static Outer()
{
Console.WriteLine("In Outer..cctor"); // This runs first
}
} Otherwise, the Test method would be able to observe that I believe this is implied by knowing that the compiler implements this by inserting IL To be clear, the static constructor(s) that are running are running as part of the call to the annotated module initializer method. |
- No *parameter_list*. | ||
- A *return_type* of `void`. | ||
- No *type_parameter_list*. | ||
- Not be declared inside a *class_declaration* having a *type_parameter_list*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not within a struct_declaration having a type_parameter_list, either.
I think it's better to specify this as a semantic constraint "Not be a member of a generic type" than as a grammar constraint.
A module initializer shall have the following characteristics: | ||
|
||
- The *method_modifier* `static`. | ||
- No *parameter_list*. | ||
- A *return_type* of `void`. | ||
- No *type_parameter_list*. | ||
- Not be declared inside a *class_declaration* having a *type_parameter_list*. | ||
- Be accessible from the containing module (that is, have an access modifier `internal` or `public`). | ||
- Not be a local function. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In C# 11, this spec will also have to disallow virtual
and abstract
, which otherwise become allowed in static methods of interfaces.
Note that the requirements bullet list in the description of "The ModuleInitializer attribute" refers to the grammar rule parameter_list, which is defined by the V9 feature "Records."
We say, "A module may have multiple initializers, which are called in an implementation-defined order." However, we do not say anything about their order w.r.t execution of static constructors. Should we? My (limited) testing showed that a static constructor in a type T executed before any module initializers in that type.