String.Empty和“”(空字符串)有什么区别?


288

在.NET中,String.Empty和之间有什么区别"",并且它们可以互换,或者是否存在一些等同于基础的引用或本地化问题,这些问题String.Empty将确保这些问题不成问题?


2
真正的问题不是什么,而是为什么。为什么Microsoft提出了,string.Empty以及将其声明为readonly而不是的原因是什么const
user1451111 '18

Answers:


294

在2.0版之前的.NET中,""创建对象时不string.Empty创建对象ref,从而string.Empty提高了效率。

在.NET 2.0版和更高版本中,所有出现的""引用都指向相同的字符串文字,这意味着""等效于.Empty,但速度不如.Length == 0

.Length == 0是最快的选择,但.Empty代码会更简洁。

有关更多信息,请参见.NET规范


91
无论如何,由于字符串实习,“”只会创建一个对象。基本上,性能的折衷是花生-可读性更重要。
乔恩·斯基特

12
有趣的是,即使该问题对将字符串与“”或string。比较都没有任何帮助。很多人似乎都用这种方式来解释这个问题……
peSHIr 2009年

11
我会注意.Length == 0,因为如果您的字符串变量为null,它可能会引发异常。但是,如果将其与“”相对应,它将正确返回false,没有例外。
杰弗里·哈蒙

11
@JeffreyHarmon:或者您可以使用string.IsNullOrEmpty( stringVar )
Flynn1179

1
如果有人想防止不良做法测试空字符串,则可以在代码分析中启用CA1820。docs.microsoft.com/visualstudio/code-quality/…–
肖恩(Sean)

197

String.Empty和“”有什么区别,并且它们可以互换

string.Empty是一个只读字段,""而是一个编译时间常数。它们表现不同的地方是:

C#4.0或更高版本中的默认参数值

void SomeMethod(int ID, string value = string.Empty)
// Error: Default parameter value for 'value' must be a compile-time constant
{
    //... implementation
}

switch语句中的大小写表达式

string str = "";
switch(str)
{
    case string.Empty: // Error: A constant value is expected. 
        break;

    case "":
        break;

}

属性参数

[Example(String.Empty)]
// Error: An attribute argument must be a constant expression, typeof expression 
//        or array creation expression of an attribute parameter type

21
有趣的是,我认为这个旧问题不会获得任何相关的新信息。我错了
johnc

我认为您的示例#1 C#4.0或更高版本中的默认参数值实质上是示例#3 属性参数的重复,因为我相信.NET会将默认参数部署到属性中。因此,更多的基本上,你根本无法把一个(运行时间)“值”(在这种情况下,一个实例句柄,对于马虎那里)到(编译时间)的元数据。
格伦·斯莱登

@GlennSlayden,我不同意你的看法。属性初始化与普通初始化不同。这是因为String.Empty在大多数情况下,您可以将用作参数。您是对的,所有示例都表明了这一点,you simply can't put a (run-time) "value" into (compile-time) metadata而这正是示例旨在显示的内容。
浦佐助

42

先前的答案对于.NET 1.1是正确的(请查看它们链接的发布日期:2003年)。从.NET 2.0及更高版本开始,基本上没有区别。无论如何,JIT最终将引用堆上的同一对象。

根据C#规范,第2.4.4.5节:http : //msdn.microsoft.com/zh-cn/library/aa691090( VS.71) .aspx

每个字符串文字不一定会导致新的字符串实例。当两个或多个根据字符串相等运算符(第7.9.7节)等效的字符串文字出现在同一程序集中时,这些字符串文字将引用同一字符串实例。

有人甚至在布拉德·艾布拉姆(Brad Abram)的评论中提到了这一点

总之,“”与String.Empty的实际结果为零。准时制将最终解决。

我个人发现,JIT比我聪明得多,因此我尽量不要对此类微编译器优化变得太聪明。与I或C#编译器之前无法预期的那样,JIT将在更合适的时间展开for()循环,更好地删除冗余代码,内联方法等。让JIT做好它的工作:)


1
小错字?“无论如何,JIT最终都会在帮助上引用相同的对象。” 你是说“在堆上”吗?
Dana


36

String.Empty是一个只读字段,""而是const。这意味着您不能String.Empty在switch语句中使用它,因为它不是常量。


随着default关键字的出现,我们可以在不增加可读性的情况下,防止意外修改,并具有编译时常量,尽管老实说,我仍然认为String.Empty比默认值更具可读性,但键入速度较慢
Enzoaeneas

18

另一个区别是String.Empty生成较大的CIL代码。虽然用于引用“”和String.Empty的代码长度相同,但编译器并未针对String.Empty参数优化字符串连接(请参见Eric Lippert的博客文章)。以下等效功能

string foo()
{
    return "foo" + "";
}
string bar()
{
    return "bar" + string.Empty;
}

产生这个IL

.method private hidebysig instance string foo() cil managed
{
    .maxstack 8
    L_0000: ldstr "foo"
    L_0005: ret 
}
.method private hidebysig instance string bar() cil managed
{
    .maxstack 8
    L_0000: ldstr "bar"
    L_0005: ldsfld string [mscorlib]System.String::Empty
    L_000a: call string [mscorlib]System.String::Concat(string, string)
    L_000f: ret 
}

有道理,但谁会这样做?为什么要故意将空字符串连接到某些东西?
罗伯特S.17年

@RobertS。也许第二个字符串在一个内联的单独函数中。难得,我同意。
布鲁诺·马丁内斯

3
使用三元运算符并不罕见:"bar " + (ok ? "" : "error")
symbiont

13

上面的答案在技术上是正确的,但是为了获得最佳的代码可读性和最小的机会,您可能真正想使用的是String.IsNullOrEmpty(s)


3
在平等比较方面,我完全同意,但问题还在于两个概念之间的区别以及比较
johnc

1
当心“例外的最小机会”通常意味着“继续但做错事的最大机会”。例如,如果您有一个正在解析的命令行参数,并且有人--foo=$BAR用来调用您的应用程序,那么您可能想找出它们之间忘记设置环境变量和根本不传递标志的区别。string.IsNullOrEmpty通常是代码错误,您没有正确验证您的输入或正在做奇怪的事情。当您确实打算使用null或Maybe / Option类型之类的字词时,通常不应该接受空字符串。
Alastair花胶

11

我倾向于使用String.Empty而不是""出于一个简单但又不明显的原因:""而且""也不尽 相同,第一个实际上有16个零宽度字符。显然,没有合格的开发人员打算将零宽度的字符放入其代码中,但是如果确实能做到这一点,则可能是维护方面的噩梦。

笔记:


这值得更多的投票。我已经看到通过跨平台的系统集成会发生此问题。
EvilDr

8

使用String.Empty而不是""

这不仅是提高速度,还不如占用内存,但这是一个有用的技巧。该 ""是文字,以便将作为文字法:在第一次使用它创建并用于下列用途返回其引用。""无论我们使用多少次,都只会在内存中存储一个实例!我在这里看不到任何内存损失。问题在于,每次""使用时,都会执行一个比较循环以检查 ""intern池中是否已存在。另一方面,String.Empty"".NET Framework内存区域中存储的引用。 String.Empty指向VB.NET和C#应用程序的相同内存地址。那么,为什么每次"" 都有参考文献时都需要搜索参考文献?String.Empty

参考:String.Emptyvs""


2
自.net 2.0以来,情况并非如此
-nelsontruran

6

String.Empty不会创建对象,而“”会创建对象。但是,这里指出的区别是微不足道的。


4
如果您在字符串中检查string.Empty或“”,这并非易事。正如Eugene Katz指出的那样,应该真正使用String.IsNullOrEmpty。否则,您将得到意想不到的结果。
Sigur 2014年

6

“”的所有实例都是相同的,被插入的字符串文字(或者应该是)。因此,您确实不会每次使用“”时都在堆上抛出新对象,而只是创建对相同的被对象的引用。话虽如此,我更喜欢string.Empty。我认为这使代码更具可读性。



4
string mystring = "";
ldstr ""

ldstr 将新的对象引用推送到存储在元数据中的字符串文字。

string mystring = String.Empty;
ldsfld string [mscorlib]System.String::Empty

ldsfld 将静态字段的值压入评估堆栈

我倾向于使用它,String.Empty而不是""因为恕我直言,它更清晰,VB风格更少。


3

埃里克·利珀特Eric Lippert)写道(2013年6月17日):
“我在C#编译器中使用的第一个算法是处理字符串连接的优化器。不幸的是,在离开之前,我没有设法将这些优化移植到Roslyn代码库中;希望有人会做到这一点!

这是截至2019年1月的Roslyn x64的一些结果。尽管本页上其他答案的共识,但在我说完所有方法后,当前的x64 JIT似乎并没有同样地对待所有这些情况。

特别要注意的是,这些示例中只有一个实际上最终以调用了String.Concat,而我猜想这是出于模糊的正确性原因(而不是优化监督)。其他差异似乎更难解释。


default(String)+ {default(String),“”,String.Empty}

static String s00() => default(String) + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s01() => default(String) + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s02() => default(String) + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

“” + {default(String),“”,String.Empty}

static String s03() => "" + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s04() => "" + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s05() => "" + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

String.Empty + {default(String),“”,String.Empty}

static String s06() => String.Empty + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s07() => String.Empty + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s08() => String.Empty + String.Empty;
    mov  rcx,[String::Empty]
    mov  rcx,qword ptr [rcx]
    mov  qword ptr [rsp+20h],rcx
    mov  rcx,qword ptr [rsp+20h]
    mov  rdx,qword ptr [rsp+20h]
    call F330CF60                 ; <-- String.Concat
    nop
    add  rsp,28h
    ret


测试细节

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)
AMD64 Release
[MethodImpl(MethodImplOptions.NoInlining)]
'SuppressJitOptimization' = false

2

从实体框架的角度来看:EF版本6.1.3在验证时似乎对String.Empty和“”的对待不同。

string.Empty出于验证目的被视为null值,如果将其用于“必需”(归因)字段,则会抛出验证错误;其中的“”将通过验证,并且不会引发错误。

在EF 7+中可以解决此问题。参考:-https : //github.com/aspnet/EntityFramework/issues/2610)。

编辑:[Required(AllowEmptyStrings = true)]将解决此问题,允许string.Empty进行验证。


2

由于String.Empty不是编译时常量,因此不能将其用作函数定义中的默认值。

public void test(int i=0,string s="")
    {
      // Function Body
    }

1
只是该答案的摘要:public void test(int i=0, string s=string.Empty) {}不会编译并说“'s'的默认参数值必须是编译时常量
。OP

1

当您通过代码直观地进行扫描时,“”会以彩色字符串的方式显示为彩色。string.Empty看起来像是常规的类成员访问。快速浏览时,更容易发现“”或理解含义。

找出字符串(堆栈溢出着色并不是完全有帮助,但在VS中更明显):

var i = 30;
var f = Math.Pi;
var s = "";
var d = 22.2m;
var t = "I am some text";
var e = string.Empty;

2
您的观点很好,但是开发人员真的是在说“”吗?也许他们打算输入一些尚不为人所知的价值而忘了退货?string.Empty的优点是使您确信原始作者的真正含义是string.Empty。我知道这是一个小问题。
mark_h

另外,恶意(或粗心)开发人员可能在引号之间使用零宽度字符,而不是使用适当的转义序列\u00ad
Palec

-8

这里的每个人都给出了一些很好的理论解释。我也有类似的疑问。所以我尝试了一个基本的编码。我发现了一个不同。这是区别。

string str=null;
Console.WriteLine(str.Length);  // Exception(NullRefernceException) for pointing to null reference. 


string str = string.Empty;
Console.WriteLine(str.Length);  // 0

因此似乎“ Null”表示绝对无效,“ String.Empty”表示包含某种值,但为空。


7
请注意,这个问题是关于""string.Empty。仅在尝试判断字符串是否为空时才null提到。
Palec
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.