Upravit

Sdílet prostřednictvím


Module Initializers

Note

This article is a feature specification. The specification serves as the design document for the feature. It includes proposed specification changes, along with information needed during the design and development of the feature. These articles are published until the proposed spec changes are finalized and incorporated in the current ECMA specification.

There may be some discrepancies between the feature specification and the completed implementation. Those differences are captured in the pertinent language design meeting (LDM) notes.

You can learn more about the process for adopting feature speclets into the C# language standard in the article on the specifications.

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