内容简介:使用 C# 语言编写字符串常量的时候,你可能会发现可以使用为什么这个看起来最适合是常量的这个问题,我们需要去看 .NET Core 的源码(当然 .NET Framework 也是一样的)。
使用 C# 语言编写字符串常量的时候,你可能会发现可以使用 ""
而不能使用 string.Empty
。进一步可以发现 string.Empty
实际上是一个静态只读字段,而不是一个常量。
为什么这个看起来最适合是常量的 string.Empty
,竟然使用静态只读字段呢?
string.Empty
这个问题,我们需要去看 .NET Core 的源码(当然 .NET Framework 也是一样的)。
[Intrinsic] public static readonly string Empty;
值得注意的是上面的 Intrinsic
特性。
Intrinsic 特性
Intrinsic
特性的注释是这样的:
Calls to methods or references to fields marked with this attribute may be replaced at some call sites with jit intrinsic expansions. Types marked with this attribute may be specially treated by the runtime/compiler.
翻译过来是:对具有此 Intrinsic
特性标记的字段的方法或引用的调用可以在某些具有 JIT 内部扩展的调用点处替换,标记有此属性的类型可能被运行时或编译器特殊处理。
也就是说, string.Empty
字段并不是一个普通的字段,对它的调用会被特殊处理。但是是如何特殊处理呢?
JIT 编译器
对 string.Empty
的注释是这样描述的:
The Empty constant holds the empty string value. It is initialized by the EE during startup. It is treated as intrinsic by the JIT as so the static constructor would never run. Leaving it uninitialized would confuse debuggers. We need to call the String constructor so that the compiler doesn’t mark this as a literal. Marking this as a literal would mean that it doesn’t show up as a field which we can access from native.
翻译过来是:
Empty 常量保存的是空字符串的值,它在启动期间由执行引擎初始化。它被 JIT 视为内在的,因此静态构造函数永远不会运行。将它保持为未初始化的状态将会使得调试器难以解释此行为。
于是我们需要调用 String 的构造函数,以便编译器不会将其标记为文字。将其标记为文字将意味着它不会显示为我们可以从本机代码访问的字段。
说明一下:
- 注释里的 EE 是 Execution Engine 的缩写,其实也就是 CLR 运行时。
-
那个 literal 我翻译成了文字。实际上这里说的是 IL 调用字符串时的一些区别:
-
在调用
""
时使用的 IL 是ldstr ""
(Load String Literal) -
而在调用
string.Empty
时使用的 IL 是ldsfld string [mscorlib]System.String::Empty
(Load Static Field)
-
在调用
-
虽然 IL 在调用
""
和string.Empty
时生成的 IL 不同,但是在 JIT 编译成本机代码的时候,生成的代码完全一样。- 详情请参见: .net - What’s the different between ldsfld and ldstr in IL? - Stack Overflow
-
我写过一篇文章 .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例 - 吕毅
。虽然一般情况下取字符串常量实例的时候会去字符串池,但是不用担心取
""
会造成性能问题,因为实际上 JIT 编译器已经特殊处理了,不会去找池子。
string.Empty
字段在整个 String
类型中你都看不到初始化的代码, String
类的静态构造函数也不会执行。也就是说, String
类中的所有静态成员都不会被托管代码初始化。 String
的静态初始化过程都是由 CLR 运行时进行的,而这部分的初始化是本机代码实现的。
那本机代码又是如何初始化 String
类型的呢?在 CLR 运行时的 AppDomain::SetupSharedStatics()
方法中实现,可前往 GitHub 阅读这部分的源码:
// This is a convenient place to initialize String.Empty. // It is treated as intrinsic by the JIT as so the static constructor would never run. // Leaving it uninitialized would confuse debuggers. // String should not have any static constructors. _ASSERTE(g_pStringClass->IsClassPreInited()); FieldDesc * pEmptyStringFD = MscorlibBinder::GetField(FIELD__STRING__EMPTY); OBJECTREF* pEmptyStringHandle = (OBJECTREF*) ((TADDR)pLocalModule->GetPrecomputedGCStaticsBasePointer()+pEmptyStringFD->GetOffset()); SetObjectReference( pEmptyStringHandle, StringObject::GetEmptyString(), this );
能否反射修改 string.Empty 的值?
不行!
实际上,在 .NET Framework 4.0 及以前是可以反射修改其值的,这会造成相当多的基础组件不能正常工作,在 .NET Framework 4.5 和以后的版本,以及 .NET Core 中,CLR 运行时已经不允许你做出这么出格儿的事了。
所以 ""
和 string.Empty
到底有什么区别?
从前文你可以得知,在运行时级别,这两者 没有任何区别 。
于是,当你需要一个代表 “空字符串” 含义的时候,使用 string.Empty
;而当你必须要一个常量时,就使用 ""
。
参考资料
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。