为什么 C# 的 string.Empty 是一个静态只读字段,而不是一个常量呢?

栏目: ASP.NET · 发布时间: 5年前

内容简介:使用 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 的构造函数,以便编译器不会将其标记为文字。将其标记为文字将意味着它不会显示为我们可以从本机代码访问的字段。

说明一下:

  1. 注释里的 EE 是 Execution Engine 的缩写,其实也就是 CLR 运行时。
  2. 那个 literal 我翻译成了文字。实际上这里说的是 IL 调用字符串时的一些区别:
    • 在调用 "" 时使用的 IL 是 ldstr "" (Load String Literal)
    • 而在调用 string.Empty 时使用的 IL 是 ldsfld string [mscorlib]System.String::Empty (Load Static Field)
  3. 虽然 IL 在调用 ""string.Empty 时生成的 IL 不同,但是在 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 ;而当你必须要一个常量时,就使用 ""

参考资料


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

设计之下

设计之下

搜狐新闻客户端UED团队 / 电子工业出版社 / 2014-1-1 / CNY 69.00

形而上者谓之道,形而下者谓之器。匠者,器也。处身平凡的匠人不断追求向上的设计之道。本书没有华丽的辞藻和长篇大论的理论,作者是搜狐一线的设计团队,写作过程中他们尽力还原真实的工作场景,并总结出了一些实用的经验和方法。 《设计之下》共三部分,全面讲解了用户体验设计的流程和方法。第一部分为“交互设计”,阐述了从项目启动、解析需求到原型设计的过程,并且总结了交互设计的要点:大局观、操作流程简捷、形式......一起来看看 《设计之下》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具