部分事件和构造函数

注释

本文是功能规格说明。 此规范是功能的设计文档。 它包括建议的规范变更,以及功能设计和开发过程中所需的信息。 这些文章将持续发布,直至建议的规范变更最终确定并纳入当前的 ECMA 规范。

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

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

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

概要

partial允许事件和构造函数的修饰符分隔声明和实现部分,类似于分部方法和分部属性/索引器

partial class C
{
    partial C(int x, string y);
    partial event Action<int, string> MyEvent;
}

partial class C
{
    partial C(int x, string y) { }
    partial event Action<int, string> MyEvent
    {
        add { }
        remove { }
    }
}

动机

C# 已支持分部方法、属性和索引器。 缺少部分事件和构造函数。

部分事件对于弱事件库非常有用,用户可以在其中编写定义:

partial class C
{
    [WeakEvent]
    partial event Action<int, string> MyEvent;

    void M()
    {
        RaiseMyEvent(0, "a");
    }
}

库提供的源生成器将提供实现:

partial class C
{
    private readonly WeakEvent _myEvent;

    partial event Action<int, string> MyEvent
    {
        add { _myEvent.Add(value); }
        remove { _myEvent.Remove(value); }
    }

    protected void RaiseMyEvent(int x, string y)
    {
        _myEvent.Invoke(x, y);
    }
}

部分事件和部分构造函数也可用于生成互作代码,例如在 Xamarin,用户可以在其中编写部分构造函数和事件定义:

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [Export("initWithFormat:packetCapacity:")]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity);

    [Export("create:")]
    public partial event EventHandler Created;
}

源生成器将生成绑定(在本例中为 Objective-C):

partial class AVAudioCompressedBuffer : AVAudioBuffer
{
    [BindingImpl(BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
    public partial AVAudioCompressedBuffer(AVAudioFormat format, uint packetCapacity) : base(NSObjectFlag.Empty)
    {
        // Call Objective-C runtime:
        InitializeHandle(
            global::ObjCRuntime.NativeHandle_objc_msgSendSuper_NativeHandle_UInt32(
                this.SuperHandle,
                Selector.GetHandle("initWithFormat:packetCapacity:"),
                format.GetNonNullHandle(nameof(format)),
                packetCapacity),
            "initWithFormat:packetCapacity:");
    }

    public partial event EventHandler Created
    {
        add { /* ... */ }
        remove { /* ... */ }
    }
}

详细设计

概况

事件声明语法(§15.8.1)扩展为允许 partial 修饰符:

 event_declaration
-    : attributes? event_modifier* 'event' type variable_declarators ';'
+    : attributes? event_modifier* 'partial'? 'event' type variable_declarators ';'
-    | attributes? event_modifier* 'event' type member_name
+    | attributes? event_modifier* 'partial'? 'event' type member_name
         '{' event_accessor_declarations '}'
     ;

实例构造函数声明语法(§15.11.1)扩展为允许 partial 修饰符:

 constructor_declaration
-    : attributes? constructor_modifier* constructor_declarator constructor_body
+    : attributes? constructor_modifier* 'partial'? constructor_declarator constructor_body
     ;

请注意,建议允许partial修饰符在修饰符中的任何位置,而不是仅作为最后一个修饰符(也用于方法、属性和类型声明)。

具有 partial 修饰符的事件声明据说是部分 事件声明 ,它与一个或多个 具有指定名称的部分事件 相关联(请注意,没有访问器的一个事件声明可以定义多个事件)。

修饰符的 partial 构造函数声明据说是部分 构造函数声明 ,它与 具有指定签名的部分构造函数 相关联。

当部分事件声明指定或具有修饰符时,event_accessor_declarationsextern 否则,它是一个 定义声明

部分构造函数声明据说是一个 定义声明 ,当它具有分号主体并且缺少 extern 修饰符时。 否则,它是实现声明

partial class C
{
    // defining declarations
    partial C();
    partial C(int x);
    partial event Action E, F;

    // implementing declarations
    partial C() { }
    partial C(int x) { }
    partial event Action E { add { } remove { } }
    partial event Action F { add { } remove { } }
}

只有部分成员的定义声明参与查找,并被视为在使用站点和发出元数据。 (文档注释如下详述除外)。实现声明签名用于关联主体的可为 null 分析。

部分事件或构造函数:

  • 只能声明为分部类型的成员。
  • 必须有一个定义和一个实现声明。
  • 不允许有 abstract 修饰符。
  • 无法显式实现接口成员。

部分事件不是类似于字段的事件(§15.8.2),即:

  • 它没有任何由编译器生成的支持存储或访问器。
  • 它只能在作中使用+=-=,不能用作值。

定义部分构造函数声明不能有构造函数初始值设定项(: this(): base(); §15.11.2)。

分析中断

partial允许在更多上下文中使用修饰符是一项重大更改:

class C
{
    partial F() => new partial(); // previously a method, now a constructor
    @partial F() => new partial(); // workaround to keep it a method
}

class partial { }

为了简化语言分析程序, partial 修饰符在任何类似方法的声明(即本地函数和顶级脚本方法)上都接受,即使我们不明确指定上述语法更改。

特性

生成的事件或构造函数的属性是相应位置中分部声明的组合属性。 组合属性以未指定的顺序连接,并且不会删除重复项。

attribute_targetmethod§22.3)在部分事件声明中被忽略。 访问器属性仅用于访问器声明(只能在实现声明下存在)。 请注意,param所有return事件声明上都已忽略attribute_targets。

实现声明中的调用方信息属性由编译器忽略(请注意,它适用于包括部分事件和构造函数的所有分部成员)中的部分属性建议所指定。

签名

部分成员的两个声明必须具有与分部属性类似的匹配签名

  1. 部分声明的类型和 ref 类型差异对运行时非常重要会导致编译时错误。
  2. 分部声明之间的元组元素名称差异会导致编译时错误。
  3. 声明必须具有相同的修饰符,但修饰符可能以不同的顺序显示。
    • 异常:这不适用于 extern 仅出现在实现声明上的修饰符。
  4. 部分声明签名中的所有其他语法差异都会导致编译时警告,但有以下例外情况:
    • 属性列表不需要匹配,如上文所述
    • 可为 null 的上下文差异(如不注意与批注)不会导致警告。
    • 默认参数值不需要匹配,但在实现构造函数声明具有默认参数值时报告警告(因为只有定义声明参与查找后,才会忽略这些参数值)。
  5. 当参数名称在定义和实现构造函数声明时发生警告。
  6. Null 性差异(不涉及未知 Null 性)会导致警告。

文档注释

允许对定义和实现声明包含文档注释。 请注意,事件访问器不支持文档注释。

当文档注释仅存在于部分成员的声明之一上时,这些文档注释通常使用(通过 Roslyn API 显示,发送到文档 XML 文件)。

当文档注释同时存在于部分成员的两个声明中时,将删除定义声明上的所有文档注释,并且仅使用有关实现声明的文档注释。

当参数名称在部分成员的声明之间有所不同时, paramref 元素将使用与源代码中的文档注释关联的声明的参数名称。 例如, paramref 对实现声明放置的文档注释使用其参数名称引用实现声明的参数符号。 这可能会令人困惑,因为元数据签名将使用定义声明中的参数名称。 建议确保参数名称在部分成员的声明中匹配,以避免这种混淆。

开放性问题

成员类型

是否需要部分事件、构造函数、运算符、字段? 建议前两种成员类型,但可以考虑任何其他子集。

还可以考虑部分 构造函数,例如,允许用户对多个分部类型声明具有相同的参数列表。

属性位置

我们应该识别部分事件的属性目标说明符(还是只识别 [method:] 定义声明)? 然后,生成的访问器属性是 method两个(或只是定义)声明部分的 -targeting 属性的串联,以及实现声明的访问器中的自定位和 method目标属性。 将不同声明类型的属性组合在一起将是史无前例的,事实上,Roslyn 中属性匹配的当前实现不支持这一点。

我们还可以考虑识别 [param:] 部分 [return:]事件,而不仅仅是对部分事件,以及所有类似字段的事件和外部事件。 如需了解详情,请访问 https://github.com/dotnet/roslyn/issues/77254