Skip to content

Champion: Module Initializers (VS 16.8, .NET 5) #2608

@gafter

Description

@gafter

See also #2486

Although the .NET platform has a feature that directly supports writing initialization code for the assembly (technically, the module), it is not exposed in C#. This is a rather niche scenario, but once you run into it the solutions appear to be pretty painful. I have seen reports of a number of customers (inside and outside Microsoft) struggle with the problem, and there are no doubt more undocumented cases.

I suggest that we would add a tiny feature to support this without any explicit syntax, by having the C# compiler recognize a module attribute with a well-known name, like the following:

namespace System.Runtime.CompilerServices
{
    [Obsolete("This attribute is only to be used in C# language version 9.0 or later", true)]
    [AttributeUsage(AttributeTargets.Module, AllowMultiple = false)]
    public class ModuleInitializerAttribute : Attribute
    {
        public ModuleInitializerAttribute(Type type) { }
    }
}

You would use it like this

[module: System.Runtime.CompilerServices.ModuleInitializerAttribute(typeof(MyModuleInitializer))]

internal static class MyModuleInitializer
{
    static MyModuleInitializer()
    {
        // put your module initializer here
    }
}

and the C# compiler would then emit a module constructor that causes the static constructor of the identified type to be triggered:

void .cctor()
{
    // synthesize and call a dummy method with an unspeakable name,
    // which will cause the runtime to call the static constructor
    MyModuleInitializer.<TriggerClassConstructor>();
}

Open issues

  • Should we permit multiple types to be decorated with ModuleInitializerAttribute in a compilation? If so, in what order should the static constructors be invoked?

Alternative Approaches

There are a number of possible ways of exposing this feature in the language:

1. Special global method declaration

A module initializer would be provided by writing a special kind of method in the global scope:

internal void operator init() ...

This gives the new language construct its own syntax. However, given how rare and niche the scenario is, this is probably far too heavyweight an approach.

2. Attribute on the type to be initialized

Instead of a module-level attribute, perhaps the attribute would be placed on the type to be initialized

[ModuleInitializer]
class ToInitialize
{
    static ToInitialize() ...
}

With this approach, we would either need to reject a program that contains more than one application of this attribute, or provide some policy to define the ordering in case it is used multiple times. Either way, it is more complex than the original proposal above.

3. Attribute on a static method to be called

Instead of a module-level attribute, perhaps the attribute would be placed on the method to be called to perform the initialization

class Any
{
    [ModuleInitializer]
    static void Initializer() ...
}

As in the previous approach, we would either need to reject a program that contains more than one application of this attribute, or provide some policy to define the ordering in case it is used multiple times. Either way, it is more complex than the original proposal.

4. Original Proposal

The original proposal naturally prevents the user from declaring more than one initializer class without adding any special language rules to accomplish that. It is very lightweight in that it uses existing syntax and semantic rules for attributes.

LDM notes:

Metadata

Metadata

Assignees

Labels

Implemented Needs ECMA SpecThis feature has been implemented in C#, but still needs to be merged into the ECMA specificationProposal champion

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions