【问题标题】:What is the difference between String.Empty and "" (empty string)?String.Empty 和 "" (空字符串)有什么区别?
【发布时间】:2010-09-14 04:01:55
【问题描述】:

在 .NET 中,String.Empty"" 之间有什么区别,它们是否可以互换,或者是否存在一些围绕相等性的潜在引用或本地化问题,String.Empty 将确保不是问题?

【问题讨论】:

  • 真正的问题不是what,而是why。为什么微软提出string.Empty 以及将其声明为readonly 而不是const 的理由是什么。
  • 几个答案似乎是针对检查字符串是否为空,这是一个比一般使用空字符串更具体的问题。

标签: .net double-quotes string


【解决方案1】:

之前的答案对于 .NET 1.1 是正确的(查看他们链接的帖子的日期:2003)。从 .NET 2.0 及更高版本开始,基本上没有区别。无论如何,JIT 最终都会引用堆上的同一个对象。

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

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

有人甚至在 Brad Abram 的帖子中提到了这一点

综上所述,"" 与 String.Empty 的实际结果是 nil。 JIT 最终会解决的。

就我个人而言,我发现 JIT 比我聪明得多,所以我尽量不要对微编译器这样的优化过于聪明。与我或 C# 编译器之前预期的相比,JIT 将在更合适的时间更好地展开 for() 循环、删除冗余代码、内联方法等。让 JIT 完成它的工作 :)

【讨论】:

【解决方案2】:

感谢您提供非常丰富的答案。

如果我错了,请原谅我的无知。我正在使用 VB,但我认为如果您测试未分配字符串的长度(即 IS Nothing),它会返回错误。现在,我在 1969 年开始编程,所以我已经远远落后了,但是我总是通过连接一个空字符串 ("") 来测试字符串。例如。 (任何语言):-

if string + "" = ""

【讨论】:

    【解决方案3】:

    在 .NET 2.0 之前的版本中,"" 创建一个对象,而string.Empty 不创建任何对象ref,这使得string.Empty 更高效。

    在 .NET 2.0 及更高版本中,所有出现的"" 都指代相同的字符串文字,这意味着"" 等价于.Empty,但仍不如.Length == 0 快。

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

    请参阅.NET specification for more information

    【讨论】:

    • "" 由于字符串实习,无论如何只会创建一次对象。基本上性能权衡是花生 - 可读性更重要。
    • 有趣的是,尽管问题没有说明将字符串与 "" 或 string.Empty 进行比较以检查空字符串,但很多人似乎都是这样解释问题的......跨度>
    • 我会小心使用 .Length == 0,因为如果您的字符串变量为空,它可能会引发异常。但是,如果您将其与 "" 进行检查,它将正确地返回 false ,无一例外。
    • @JeffreyHarmon:或者你可以使用string.IsNullOrEmpty( stringVar )
    • 如果有人想防止不良做法来测试空字符串,您可以在代码分析中启用 CA1820。 docs.microsoft.com/visualstudio/code-quality/…
    【解决方案4】:

    Eric Lippert wrote(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
    

    【讨论】:

    • 使用 sharplab.io 进行的快速检查表明,Core CLR 6.0.21.52210 对 s00 到 s07 的处理方式相同(生成的代码与此处的 s00 匹配),但仍将 String.Concat() 用于 s08。
    【解决方案5】:

    当您在视觉上扫描代码时,“”会像字符串一样被着色。 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;
    

    【讨论】:

    • 你说得很好,但开发人员真的是指“”吗?也许他们打算放入一些未知的价值而忘记返回? string.Empty 的优势在于让您确信原作者的真正意思是 string.Empty。我知道这是一个小问题。
    • 此外,恶意(或粗心)的开发人员可能会在引号之间放置一个零宽度字符,而不是使用适当的转义序列,如 \u00ad
    【解决方案6】:

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

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

    【讨论】:

    • 只是这个答案的摘要:public void test(int i=0, string s=string.Empty) {} 不会编译并说“'s' 的默认参数值必须是编译时常量。OP 的答案有效。
    【解决方案7】:

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

    string.Empty 出于验证目的被视为空值,如果在必需(属性)字段上使用它会引发验证错误;其中 "" 将通过验证而不抛出错误。

    这个问题可能在 EF 7+ 中得到解决。参考: -https://github.com/aspnet/EntityFramework/issues/2610)。

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

    【讨论】:

      【解决方案8】:

      使用String.Empty 而不是""

      这更多的是为了速度而不是内存使用,但它是一个有用的提示。这 "" 是文字,因此将充当文字:第一次使用时 创建并为以下用途返回其引用。只有一个 无论我们多少次,"" 的实例都将存储在内存中 用它!我在这里看不到任何内存损失。 问题是 每次使用"",都会执行一个比较循环来检查是否 "" 已经在实习生池中。 在另一边,String.Empty 是对存储在 .NET Framework 内存区域中的"" 的引用。 String.Empty 指向 VB.NET 和 C# 的相同内存地址 应用程序。那么为什么每次需要""时都搜索参考 当您在String.Empty 中有该参考资料时?

      参考:String.Empty vs ""

      【讨论】:

      • 自 .net 2.0 以来这不是真的
      • 如果需要,搜索引用肯定会在编译时发生,因此运行时开销为零。但我可能期望更好:将"" 识别为与string.Empty 相同,从而能够立即发出正确的引用。
      【解决方案9】:

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

      【讨论】:

      • 随着 default 关键字的出现,我们可以提高可读性,防止意外修改,并具有编译时常量,但老实说,我仍然认为 String.Empty 比 String.Empty 更具可读性默认,但输入速度较慢
      • @Enzoaeneas A default 字符串为空,不为空。所有引用对象的default 为空。
      【解决方案10】:

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

      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”意味着它包含某种值,但它是空的。

      【讨论】:

      • 请注意,问题是关于 ""string.Empty。只有在试图判断字符串是否为空时,才会提到null
      【解决方案11】:

      我倾向于使用String.Empty 而不是"",原因很简单,但并不明显: """" 不一样,第一个实际上有 16 个零宽度字符。显然,没有一个称职的开发人员会在他们的代码中加入零宽度字符,但如果他们确实进入了那里,那可能是维护的噩梦。

      注意事项:

      【讨论】:

      • 这值得更多的支持。我已经通过跨平台的系统集成看到了这个问题。
      【解决方案12】:
      string mystring = "";
      ldstr ""
      

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

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

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

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

      【讨论】:

        【解决方案13】:

        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语句中的case表达式

        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
        

        【讨论】:

        • 有趣,我不认为这个老问题会得到任何相关的新信息。我错了
        • 我认为您的示例 #1 C# 4.0 或更高版本中的默认参数值本质上是您的示例 #3 属性参数,因为我相信.NET 将默认参数部署到属性中。所以更基本上,您根本不能将(运行时)“值”(在这种情况下,一个实例 handle,用于那些坚持者)到(编译时)元数据中。
        • @GlennSlayden,我不同意你的看法。属性初始化不同于普通的初始化。这是因为在大多数情况下,您可以将 String.Empty 作为参数传递。你是对的,所有的例子都表明you simply can't put a (run-time) "value" into (compile-time) metadata,这就是例子的目的。
        • 那些看起来不像他们的行为方式与我不同。只有一种情况是合法的,另一种是不合法的。对我来说,说它们的行为不同,就是说两者都构成合法代码,但在运行时可能存在不同的行为,具体取决于您使用的行为。
        • 知道为什么 string.Empty 是一个只读字段会很有趣。似乎是编译时常量的理想场所。不像string.Empty永远需要在运行时计算,对吧?
        【解决方案14】:

        另一个区别是 String.Empty 生成更大的 CIL 代码。虽然引用 "" 和 String.Empty 的代码长度相同,但编译器不会优化 String.Empty 参数的字符串连接(请参阅 Eric Lippert 的 blog post)。以下等价函数

        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 
        }
        

        【讨论】:

        • 有效点,但谁会做这样的事情?为什么要故意将空字符串连接到某个东西?
        • @RobertS。也许第二个字符串在一个单独的内联函数中。这很罕见,我承认。
        • 使用三元运算符并不少见:"bar " + (ok ? "" : "error")
        • 但是在三元运算符的情况下,除非ok 是编译时常量,否则生成的IL 无论如何都需要使用string.Concat。
        【解决方案15】:

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

        【讨论】:

        • 在相等比较方面,我完全同意,但问题也是关于两个概念之间的区别以及比较
        • 请注意,“发生异常的可能性最小”通常意味着“继续进行但做错事的可能性最大”。例如,如果您有一个正在解析的命令行参数,并且有人用--foo=$BAR 调用您的应用程序,那么您可能想找出他们忘记设置环境变量和根本不传递标志之间的区别。 string.IsNullOrEmpty 通常是您没有正确验证输入或正在做奇怪的事情的代码气味。当您真正想要使用 null 或类似 Maybe / Option 类型时,通常不应接受空字符串。
        【解决方案16】:

        "" 的所有实例都是相同的内部字符串字面量(或者它们应该是)。所以你真的不会每次使用 "" 时都在堆上抛出一个新对象,而只是创建对同一个内部对象的引用。话虽如此,我更喜欢 string.Empty。我认为它使代码更具可读性。

        【讨论】:

          【解决方案17】:
          【解决方案18】:

          String.Empty 不会创建对象,而 "" 会。然而,正如here 指出的那样,差异是微不足道的。

          【讨论】:

          • 如果您检查字符串中的 string.Empty 或“”,这并非易事。正如 Eugene Katz 指出的那样,应该真正使用 String.IsNullOrEmpty。否则你会得到意想不到的结果。
          • @Sigur 如何使用String.IsNullOrEmpty 将变量设置为空字符串,或者将空字符串作为函数参数传递?
          猜你喜欢
          • 2014-06-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-13
          • 2011-02-26
          • 1970-01-01
          • 1970-01-01
          • 2010-09-17
          相关资源
          最近更新 更多