模块初始值设定项Module Initializers

总结Summary

尽管 .NET 平台具有直接支持编写程序集的初始化代码的 功能 (技术上的模块) ,但它不是用 c # 公开的。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. 在 Microsoft 内部和外部 (有 许多客户) 遇到此问题,并且没有任何不清楚记录的情况。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
  • 当前构造函数方法的一个特别难点 static 是,运行时必须对使用静态构造函数的类型使用额外的检查,以确定是否需要运行静态构造函数。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

通过使用属性修饰方法,可以将方法指定为模块初始值设定项 [ModuleInitializer]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. 方法必须是 staticThe method must be static.
  2. 方法必须是无参数的。The method must be parameterless.
  3. 该方法必须返回 voidThe 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.
    • 这意味着该方法的有效可访问性必须为 internalpublicThis 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

2020年4月8日April 8th, 2020