模块初始值设定项

注意

本文是特性规范。 该规范充当该功能的设计文档。 它包括建议的规范更改,以及功能设计和开发过程中所需的信息。 这些文章将发布,直到建议的规范更改最终确定并合并到当前的 ECMA 规范中。

功能规范与已完成的实现之间可能存在一些差异。 这些差异在相关的 语言设计会议(LDM)记录中进行了记录。

可以在 规范一文中详细了解将功能规范采用 C# 语言标准的过程。

支持者问题:https://github.com/dotnet/csharplang/issues/2608

总结

尽管 .NET 平台具有 功能,它直接支持为程序集编写初始化代码(从技术上而言是模块),但它不会在 C# 中公开。 这是一种相当小众的情况,但一旦遇到了,解决办法似乎很是棘手。 有报道称,许多客户(包括 Microsoft 内部和外部)都在努力解决这个问题,毫无疑问,还有更多未记录的案例。

动机

  • 使库能够在加载时执行快速一次性初始化,尽可能减少开销,用户无需显式调用。
  • 当前 static 构造函数方法的一个特定难题是,运行时必须对类型与静态构造函数的使用进行额外的检查,以确定是否需要运行静态构造函数。 这增加了可衡量的开销。
  • 使源生成器能够运行某些全局初始化逻辑,而无需用户显式调用任何内容

详细设计

通过使用 [ModuleInitializer] 属性修饰方法,可以将方法指定为模块初始值设定项。

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

可以使用该属性,如下所示:

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

对以此属性为目标的方法施加了一些要求:

  1. 该方法必须是 static
  2. 该方法必须是无参数的。
  3. 该方法必须返回 void
  4. 该方法不得为泛型或包含在泛型类型中。
  5. 必须可从包含模块访问该方法。
    • 这意味着该方法的有效访问性必须是 internalpublic
    • 这也意味着该方法不能是本地函数。

在编译中找到具有此属性的一个或多个有效方法时,编译器将发出调用每个特性化方法的模块初始值设定项。 调用将以保留但确定的顺序发出。

缺点

为什么我们这样做?

  • 也许现有的用于“注入”模块初始值设定项的第三方工具足以满足要求此功能的用户。

设计会议

2020 年 4 月 8 日