C#中不同static readonly属性实现与字段的运行时行为差异
问题1解答
你的认知完全正确。UserId1是仅包含自定义get访问器的静态属性,每次访问该属性时,都会执行getter内部的new Guid(...)逻辑,生成一个全新的Guid实例,哪怕每次生成的值完全相同,这个实例化过程也会重复执行。
而UserId2是带初始化器的自动实现只读属性,C#编译器会自动为它生成一个私有静态只读的 backing 字段,对应的Guid.Parse初始化逻辑会被注入到UserProperties静态类的构造函数中,在整个程序生命周期内仅会执行一次,后续所有对UserId2的访问,都是直接返回该backing字段的缓存值,不会重复生成实例。
问题2解答
UserId3相比UserId2确实存在两方面差异:
- 语义层面:
UserId3本身就是静态只读字段,是专门用来表示“类级别固定不变的值”的语义;而自动属性的设计初衷是封装访问逻辑,适合未来可能需要调整取值逻辑的场景,如果确定值永远不需要修改,用静态只读字段语义更精准。 - 执行层面:未开启优化的Debug模式下,访问
UserId2确实会多一次getter方法调用的开销(IL层面为call get_UserId2指令,方法内部才是加载backing字段的逻辑),而访问UserId3是直接读取静态字段的ldsfld指令,少了一层调用。不过这个开销极小,绝大多数场景下可以忽略。
问题3解答
三者的差异不会被完全抹平,具体分情况:
UserId1和另外两者的指令永远不会相同:因为它的语义就是每次访问都要生成新的Guid实例,编译器和JIT的优化都不能改变程序的可观测行为,所以不可能将它优化为单次初始化的逻辑。UserId2和UserId3在Release优化模式下会生成完全一致的机器指令:JIT会对只有单条返回字段逻辑的简单属性getter做内联优化,直接把属性调用替换成读取底层backing字段的操作,此时访问两者的执行效率没有任何区别。
内容的提问来源于stack exchange,提问作者Marc Ranolfi
相关产品推荐
相关产品推荐

