Module Initializers

Summary

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. There are reports of a number of customers (inside and outside Microsoft) struggling with the problem, and there are no doubt more undocumented cases.

Motivation

  • Enable libraries to do eager, one-time initialization when loaded, with minimal overhead and without the user needing to explicitly call anything
  • One particular pain point of current static constructor approaches is that the runtime must do additional checks on usage of a type with a static constructor, in order to decide whether the static constructor needs to be run or not. This adds measurable overhead.
  • Enable source generators to run some global initialization logic without the user needing to explicitly call anything

Detailed design

A method can be designated as a module initializer by decorating it with a [ModuleInitializer] attribute.

using System;
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class ModuleInitializerAttribute : Attribute { }
}

The attribute can be used like this:

using System.Runtime.CompilerServices;
class C
{
    [ModuleInitializer]
    internal static void M1()
    {
        // ...
    }
}

Some requirements are imposed on the method targeted with this attribute:

  1. The method must be static.
  2. The method must be parameterless.
  3. The method must return void.
  4. The method must not be generic or be contained in a generic type.
  5. The method must be accessible from the containing module.
    • This means the method's effective accessibility must be internal or public.
    • This also means the method cannot be a local function.

When one or more valid methods with this attribute are found in a compilation, the compiler will emit a module initializer which calls each of the attributed methods. The calls will be emitted in a reserved, but deterministic order.

Drawbacks

Why should we not do this?

  • Perhaps the existing third-party tooling for "injecting" module initializers is sufficient for users who have been asking for this feature.

Design meetings

April 8th, 2020