为什么将属性从“init”更改为“set”是二进制破坏性更改?

MS *_*nth 22 c# properties abi

时隔很长一段时间,我又回到了 C#,并试图使用《C# 10 in a Nutshell》一书来跟上进度。

\n

作者在那里提到,将属性的访问器从 更改为initset反之亦然是一个重大更改。我可以理解如何将其更改为set可能init是一个重大更改,但我只是无法理解为什么以其他方式更改它会是一个重大更改。

\n

例如:

\n
// Assembly 1\nTest obj = new(){A = 20};\n\n// Assembly 2\nclass Test\n{\n   public int A {get; init;} = 10;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

即使我将init属性访问器更改为.Assembly 1 中的此代码也不应受到影响set。那么为什么这是一个重大变化呢?

\n

Swe*_*per 20

这是因为init访问器被编译成带有modreq声明的设置器。int 属性的 IL 代码P可能如下所示(请参阅SharpLab):

.method public hidebysig specialname    
    instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_P (    
        int32 'value'   
    ) cil managed   
{   
    ...
}
Run Code Online (Sandbox Code Playgroud)

注意令牌modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit)

普通的 setter 不会生成 this modreq

在调用方方面,modreq当且仅当modreq该方法中存在 a 时,调用指令必须提供声明作为要调用的事物的签名的一部分。因此,对访问器的调用init将如下所示:

callvirt instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) SomeClass::set_P(int32)
Run Code Online (Sandbox Code Playgroud)

不仅仅是

callvirt instance void SomeClass::set_P(int32)
Run Code Online (Sandbox Code Playgroud)

如果更改为 setter,则init必须更改对访问器的所有调用以删除modreq,否则将无法正确解析该方法。因此,这是一个重大变化。

至于为什么modreq使用 代替常规属性来标记属性,请参阅规范草案中的这一部分。总而言之,这是二进制兼容性和“不知道访问器的编译器会做什么”之间的权衡init。最后,他们决定牺牲二进制兼容性,以便不了解的编译器init 不允许设置该属性的代码。


Mat*_*son 11

这记录在设计说明中,特别是标题为“重大更改”的部分中。

在元数据期间发出时,init 属性访问器的发出策略必须在使用属性或 modreq 之间进行选择。这些有不同的权衡需要考虑。

使用 modreq 声明注释属性集访问器意味着 CLI 兼容编译器将忽略该访问器,除非它理解 modreq。这意味着只有知道 init 的编译器才会读取该成员。不知道 init 的编译器将忽略 set 访问器,因此不会意外地将属性视为读/写。

modreq 的缺点是 init 成为 set 访问器的二进制签名的一部分。添加或删除 init 将破坏应用程序的二进制兼容性。

最终的决定是:

鉴于也没有对删除 init 作为二进制兼容更改的重要支持,因此选择直接使用 modreq。

因此他们modreq在实现中使用了,结果用替换initset是一个二进制破坏性更改。

这里有更多的讨论modreq

如果您确实想了解更多信息,可以在CLI 规范modreq中查找一些详细信息。