为何Roslyn编译器生成的C#本地函数IL访问修饰符是internal而非private
为什么Roslyn编译器生成的本地函数在IL中访问修饰符为
internal而非private? 首先给出相关示例代码和生成的IL对照:
C# 示例代码:
private void M() { bool f = true; bool x1() => f; static bool x2() => true; }
对应生成的IL代码:
.method assembly hidebysig static bool '<M>g__x1|0_0' ( valuetype C/'<>c__DisplayClass0_0'& '' ) cil managed { ... } .method assembly hidebysig static bool '<M>g__x2|0_1' () cil managed { ... }
这个差异本质是C#语言层面的访问语义定义和编译器底层IL实现细节的区别,二者并不冲突,具体原因如下:
- 微软官方文档提到的「本地函数是private的」是面向C#开发者的语义描述:它指的是本地函数仅在定义它的父方法内部可访问,C#编译器会在编译期强制校验这个规则,你既无法在父方法外调用本地函数,也不能主动给本地函数加
private等访问修饰符,这个语义约束和IL层面的访问修饰符没有直接关系。 - IL层面选择
internal(对应IL的assembly标记)修饰符是出于实现层面的兼容性和性能考量:- 当本地函数捕获了父方法的变量时(比如示例中的
x1),Roslyn会生成对应的闭包显示类(示例中的C/<>c__DisplayClass0_0)来存储捕获的变量。如果本地函数生成private修饰符,按照CLR的访问规则,只有它所属的类能访问该方法,同程序集的闭包显示类调用该方法时会触发额外的权限校验,增加不必要的运行时开销,使用internal可以直接避免这个问题。 - 无论本地函数是否捕获变量、是否是静态本地函数,统一生成
internal修饰符可以简化Roslyn的实现逻辑,不需要针对不同场景做分支处理,降低实现复杂度。
- 当本地函数捕获了父方法的变量时(比如示例中的
- 这种实现完全不会破坏C#的访问约束:Roslyn生成的本地函数都会用
<M>g__x1|0_0这类包含特殊字符的混淆命名,C#语法本身不允许用户声明或调用这类名称的方法,所以哪怕是internal修饰符,也不会出现用户代码意外访问到本地函数的情况。
内容的提问来源于stack exchange,提问作者OwnageIsMagic
相关产品推荐
相关产品推荐

