【问题标题】:Empty string as a special case?空字符串作为特例?
【发布时间】:2014-03-03 03:26:37
【问题描述】:

我阅读了 Jon Skeet 的测验,我想知道为什么我的第二个样本不起作用而第一个样本起作用。

为什么会产生true

object x = new string("".ToArray());
object y = new string("".ToArray());
Console.WriteLine(x == y); //true

但是这个没有:

var k="k";
//string.intern(k); // doesn't help
object x = new string(k.ToArray());
object y = new string(k.ToArray());
Console.WriteLine(x == y); //false

我正在使用 fw 4.5 和 vs2010。

幸运的是,我也安装了 vs2005,结果相同:

【问题讨论】:

  • 如果你像这样比较x.Equals(y)那么它将返回true
  • 那么如果按值比较的话,“k”字符串不应该等于另一个“k”字符串吗?
  • 我不确定您为什么接受下面的答案,它甚至没有解决问题的内容。在您的第一个示例中,您使用字符串构造函数来实例化空字符串。那应该给出全新的参考,而不是相同的。
  • 我做到了,但文字 ""String.Emptynew String(new char[0]) 是不同的野兽。据我所知,没有一个答案真正解决了这个问题。来自 Eric Lippert 博客的引用被断章取义。它谈论的是空字符串文字和String.Empty。两个独立更新的字符串恰好为空的情况仍不清楚。所有答案都围绕着这个解释轻松地跳了起来。
  • 是的,我可能对那一点太多了。无论出于何种原因,在博客的上下文中阅读它对我来说都是不同的。 new T() != new T() 对所有 T 并不总是正确的想法现在让我大吃一惊。

标签: c# .net


【解决方案1】:

这是 Eric Lippert 的一篇博文,它回答了您的问题:String interning and String.Empty

他描述了类似的情况:

object obj = "Int32";
string str1 = "Int32";
string str2 = typeof(int).Name;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // false !?

所以想法是,实习并不意味着您将只有一个特定 string 的实例,即使它已被实习。 默认情况下只保留编译时文字。这意味着以下代码打印为true:

var k1 = "k";
object k2 = "k";
Console.WriteLine(k1 == k2);

但是,如果您尝试在运行时以编程方式创建包含 "k" 内容的字符串,例如使用string(char[]) 构造函数,在对象上调用ToString(),使用StringBuilder 等,默认情况下您不会得到实习字符串。这个打印错误;

var k1 = "k";
object k2 = new string("k".ToCharArray());
Console.WriteLine(k1 == k2);

为什么?因为在运行时实习字符串很昂贵。

没有免费的午餐。

(...)

简而言之,一般情况下不值得实习所有字符串。

关于空字符串的不同行为:

.NET 运行时的某些版本会在运行时自动对空字符串进行 intern,而有些则不会!

【讨论】:

  • 那为什么 Console.WriteLine(str1 == str2);打印真
  • @Him 因为它们被声明为stringString 类覆盖== 运算符来执行字符串比较,而不是引用相等性检查。
  • 这与这个问题有什么关系?在给出的示例中,字符串都是通过构造函数创建的。没有使用字符串文字(因此没有实习)。
  • 请解释ReferenceEquals(new string(new char[]{}), new string(new char[]{})) == true 是如何实现的。我猜这与参考源代码中标记为extern 的构造函数有关——也就是说,它不能用纯C# 来实现,对吧?
  • @JeffMercado,也许零大小和相同类型的数组和字符串一样被保留?
【解决方案2】:

注意,在第二个代码块中插入 new 字符串确实使它们相等。

var k="k";
object x = string.Intern(new string(k.ToArray()));
object y = string.Intern(new string(k.ToArray()));
Console.WriteLine(x == y); //true

它似乎是在自动对空字符串进行实习,但非空字符串不会被实习,除非它们被明确地完成(或者它们是始终被实习的文字字符串)。

我猜是的,空字符串被视为特殊情况并被自动实习,可能是因为检查非常简单,不会增加任何真正的性能损失(我们可以肯定地说,任何字符串长度 0 是空字符串,与任何其他空字符串相同——所有其他字符串都要求我们查看字符而不仅仅是长度。

【讨论】:

  • 默认情况下,所有常量字符串字面量都是实习的。
  • 这将返回 true,因为您的代码与默认情况下实习生的 object x = "k"; object y = "k" 相同。问题是为什么 new new string(k.ToArray()) 提供新实例而 new string("".ToArray()) 提供实习实例?
【解决方案3】:

第一种情况比较同一个对象的 2 个引用 (String.Empty)。为 2 个 object 变量调用 operator== 会导致它们通过引用进行比较并给出 true

第二种情况产生两个不同的字符串类实例。他们的参考比较给出了false

如果您将string 类型分配给xy,则在第二种情况下,将调用string.operator== 覆盖并且比较结果为true

请注意,在这两种情况下,我们都不会直接处理字符串实习。我们比较的字符串对象是使用string(char[]) 构造函数创建的。显然,该构造函数旨在在使用空数组作为参数调用时返回 string.Empty 字段的值。

MarcinJuraszek 发布的答案是指讨论字符串实习的Lippert's blog。该博客文章讨论了字符串类使用的其他极端情况。考虑前面提到的 Lippert 博客中的这个例子:

object obj = "";
string str1 = "";
string str2 = String.Empty;
Console.WriteLine(obj == str1); // true
Console.WriteLine(str1 == str2); // true
Console.WriteLine(obj == str2); // sometimes true, sometimes false?!

我们在这里看到的是,来自空字符串文字 ("") 的赋值不能保证产生对静态只读 System.String.Empty 字段的引用。

让我们看看 object x = new string("".ToArray()); 表达式的 IL:

IL_0001:  ldstr      ""
IL_0006:  call       !!0[] [System.Core]System.Linq.Enumerable::ToArray<char>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_000b:  newobj     instance void [mscorlib]System.String::.ctor(char[])
IL_0010:  stloc.0

实习可能(或可能不会)发生在 IL_0001 行。无论文字是否被实习,ToArray() 方法都会生成一个新的空数组,String::.ctor(char[]) 会为我们提供String.Empty

我们在这里看到的不是string.Empty 的特例,而是string 类同时是引用类型和不可变的副作用之一。还有其他不可变的框架类型具有具有相似语义的预定义值(如DateTime.MinValue)。但据我所知,此类框架类型被定义为struct,与string 不同,后者是引用类型。值类型是完全不同的故事......从可变类构造函数返回一些固定的预定义类型实例是没有意义的(调用代码将能够更改该实例并导致类型的不可预测的行为)。因此,如果这些类型是不可变的,那么构造函数并不总是返回新实例的 reference 类型可能存在。我不知道框架中的其他此类类型,除了string

【讨论】:

  • 第二种情况产生两个不同的字符串类实例 OK。如果是这样 - 为什么第一个不产生2个不同的字符串类实例
  • @RoyiNamir 可能 string(char[]) ctor 是这样设计的。 .net 具有特殊的空字符串对象。所以当构造函数接收到空数组时返回string.Empty
  • @PavelZhuravlev:还有其他类型的构造函数并不总是返回新实例吗?
  • @RoyiNamir:我试图在这里澄清我的观点。请查看更新后的答案。
【解决方案4】:

我的假设是为什么第一个结果为真而第二个结果为假:

第一个结果我是一个优化,取下面的代码代码

Enumerable.Empty<char>() == Enumerable.Empty<char>() // true

所以,假设ToArray 方法在字符串为空时返回Enumerable.Empty&lt;char&gt;(),这就解释了为什么第一个结果为真而第二个不为真,因为它正在进行引用检查。

【讨论】:

    【解决方案5】:

    根据http://msdn.microsoft.com/en-us/library/system.string.intern(v=vs.110).aspx

    In the .NET Framework 3.5 Service Pack 1, the Intern method reverts to its behavior in the .NET Framework 1.0 and 1.1 with regard to interning the empty string...

    ...In the .NET Framework 1.0, .NET Framework 1.1, and .NET Framework 3.5 SP1, ~empty strings~ are equal

    这意味着,即使从空数组构造,空字符串也是默认的,因此是相等的。

    此外:

    The .NET Framework version 2.0 introduces the CompilationRelaxations.NoStringInterning enumeration member

    这很可能为您提供了一种创建一致比较方式的方法,尽管正如@BenM 所建议的,您宁愿明确使用 Intern 函数。

    鉴于发生的拳击,您也可以使用string.Equals 代替==

    【讨论】:

      【解决方案6】:

      我认为这可能是我推荐 Jon Skeet 的原因 关于字符串比较

      Are string.Equals() and == operator really same?

              object x1 = new StringBuilder("").ToString().ToArray();
              object y1 = new StringBuilder("").ToString().ToArray();
              Console.WriteLine(x1 == y1); //true
      
              Console.WriteLine("Address x1:" + Get(x1));
              Console.WriteLine("Address y1:" + Get(y1));
      
              var k = "k";
              //string.intern(k); // doesn't help
              object x = new string(k.ToArray());
              object y = new string(k.ToArray());
              Console.WriteLine(x == y); //false
      
              Console.WriteLine("Address x:" + Get(x));
              Console.WriteLine("Address y:" + Get(y));
      
              Console.Read(); 
      

      输出

      False
      Address x1:0x2613E5
      Address y1:0x2613E5
      False
      Address x:0x2613E5
      Address y:0x2613E5
      

      【讨论】:

        【解决方案7】:

        有一种特殊情况,空字符串总是返回相同的对象,这就是为什么在这种情况下比较对象是否相同的原因。

        [编辑]:之前的代码使用字符串比较器而不是对象

        object a = "s";
        object b = "d";
        
        a = ((string)a).Replace("s", "");
        b = ((string)b).Replace("d", "");
        
        Console.WriteLine(a == b);
        
        object c = "sa";
        object d = "da";
        
        c = ((string)c).Replace("s", "");
        d = ((string)d).Replace("d", "");
        
        Console.WriteLine(c == d);
        
        c = ((string)c).Replace("a", "");
        d = ((string)d).Replace("a", "");
        
        Console.WriteLine(c == d);
        

        结果

        True
        False
        True
        

        【讨论】:

          猜你喜欢
          • 2014-11-12
          • 2016-04-20
          • 2022-10-13
          • 2015-08-21
          • 2011-11-30
          • 2020-10-01
          • 2015-11-07
          • 2011-03-10
          • 1970-01-01
          相关资源
          最近更新 更多