前言

在 C# 中,string.Empty 是一个字段,表示空字符串。但是其与 "" 的区别对我来说并不是很清楚, 因此我决定使用 IL 帮助我分清其中的区别。

编写与反编译

编写实例

我通过了编写一个很简单的方法来实现了这个过程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace ClassLibrary1;

public class Class1
{
    public static string StaticMethod()
    {
        var a = "";
        var b = string.Empty;
        return a;
    }
}

为了区分普通方法和静态方法中可能存在的差异性,我选择编写了两个方法 MethodStaticMethod

反编译

感谢 Rider,我们才可以如此轻松的查看 IL 代码。我把整个的 IL 都会贴出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
.class public auto ansi beforefieldinit
  ClassLibrary1.Class1
    extends [System.Runtime]System.Object
{

  .method public hidebysig static string
    StaticMethod() cil managed
  {
    .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor([in] unsigned int8)
      = (01 00 01 00 00 ) // .....
      // unsigned int8(1) // 0x01
    .maxstack 8

    // [7 9 - 7 20]
    IL_0000: ldstr        ""

    // [8 9 - 8 30]
    IL_0005: ldsfld       string [System.Runtime]System.String::Empty
    IL_000a: pop

    // [9 9 - 9 18]
    IL_000b: ret

  } // end of method Class1::StaticMethod

  .method public hidebysig specialname rtspecialname instance void
    .ctor() cil managed
  {
    .maxstack 8

    IL_0000: ldarg.0      // this
    IL_0001: call         instance void [System.Runtime]System.Object::.ctor()
    IL_0006: ret

  } // end of method Class1::.ctor
} // end of class ClassLibrary1.Class1

分析

为了简洁,我们只分析 StaticMethod 中最核心的部分:

1
2
3
4
5
6
7
8
9
// [7 9 - 7 20]
IL_0000: ldstr        ""

// [8 9 - 8 30]
IL_0005: ldsfld       string [System.Runtime]System.String::Empty
IL_000a: pop

// [9 9 - 9 18]
IL_000b: ret

对于 "" 通过 IL 可以发现其是直接通过 ldstr 直接装载了 "" 字符串进入栈顶。而 string.Empty 则是通过 ldsfld 装载了在 [System.Runtime]System.String::Emptystring

通常来说,ldsfld 是直接装载值,而 ldsflda 才是装载一个地址。因此对于使用 string.Empty"" 在装载性能上的差异性几乎是毫无差别。

那真的没有差别了吗?

在性能上的差别已经被排除,那是否它们真的没有差别呢?答案是否定的。考虑如下代码

1
2
3
4
5
var m = str switch
{
    "" => "A",
    string.Empty => "B"
};

或者

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
string str = "";
string m;
switch(str)
{
    case "":
        m = "A";
        break;
    case string.Empty:
        m = "B";
        break;
}

其中所有 string.Empty 上都会引发错误 [CS0150] A constant value is expected

其中核心原因可以通过直接反编译 string.Empty 发现其的签名是 public static readonly string Empty;"" 则是 const

总结

对于 ""string.Empty,两者几乎不存在性能差异,唯一差别只存在于前者是常量而后者仅为只读静态字段。 而在使用中我更推荐使用 "",因为其会带来更好的可读性。