字符串比较==只能工作,因为字符串是不可变的吗?

时间:2012-01-18 16:57:45

标签: c# .net string

在比较两个字符串及其变量时,我有一个想法:

string str1 = "foofoo";
string strFoo = "foo";
string str2 = strFoo + strFoo;

// Even thought str1 and str2 reference 2 different
//objects the following assertion is true.

Debug.Assert(str1 == str2);

这纯粹是因为.NET运行时识别字符串的值是相同的,并且因为字符串是不可变的,所以str2的引用等于str1的引用?

因此,当我们str1 == str2时,我们实际比较参考和而不是值?我原本以为这是语法糖的产物,但我不正确吗?

我写的任何不准确之处?

8 个答案:

答案 0 :(得分:14)

答案在C#Spec§7.10.7

  

字符串相等运算符比较字符串值而不是字符串   引用。当两个单独的字符串实例包含完全相同的时   字符序列,字符串的值是相等的,但是   参考文献不同。如§7.10.6中所述,引用类型   可以使用相等运算符来比较字符串引用而不是   字符串值。

答案 1 :(得分:10)

没有

==有效,因为String类将==运算符重载为等于Equals方法。

来自Reflector:

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
public static bool operator ==(string a, string b)
{
    return Equals(a, b);
}

答案 2 :(得分:7)

实际上,String.Equals首先检查它是否是相同的引用,如果不是则比较内容。

答案 3 :(得分:7)

如果我们查看jitted代码,我们会看到使用str2汇编了String.Concat,并且它实际上与str1的引用不同。我们还将看到使用Equals进行比较。换句话说,断言在字符串包含相同字符时传递。

此代码

static void Main(string[] args)
{
    string str1 = "foofoo";
    string strFoo = "foo";
    string str2 = strFoo + strFoo;
    Console.WriteLine(str1 == str2);
    Debugger.Break();
}

是否适合(请向侧面滚动以查看评论)

C:\dev\sandbox\cs-console\Program.cs @ 22:
00340070 55              push    ebp
00340071 8bec            mov     ebp,esp
00340073 56              push    esi
00340074 8b3530206003    mov     esi,dword ptr ds:[3602030h] ("foofoo")  <-- Note address of "foofoo"

C:\dev\sandbox\cs-console\Program.cs @ 23:
0034007a 8b0d34206003    mov     ecx,dword ptr ds:[3602034h] ("foo")  <-- Note different address for "foo"

C:\dev\sandbox\cs-console\Program.cs @ 24:
00340080 8bd1            mov     edx,ecx
00340082 e81977fe6c      call    mscorlib_ni+0x2b77a0 (6d3277a0)     (System.String.Concat(System.String, System.String), mdToken: 0600035f)  <-- Call String.Concat to assemble str2
00340087 8bd0            mov     edx,eax
00340089 8bce            mov     ecx,esi
0034008b e870ebfd6c      call    mscorlib_ni+0x2aec00 (6d31ec00)     (System.String.Equals(System.String, System.String), mdToken: 060002d2)  <-- Compare using String.Equals
00340090 0fb6f0          movzx   esi,al
00340093 e83870f86c      call    mscorlib_ni+0x2570d0 (6d2c70d0) (System.Console.get_Out(), mdToken: 060008fd)
00340098 8bc8            mov     ecx,eax
0034009a 8bd6            mov     edx,esi
0034009c 8b01            mov     eax,dword ptr [ecx]
0034009e 8b4038          mov     eax,dword ptr [eax+38h]
003400a1 ff5010          call    dword ptr [eax+10h]

C:\dev\sandbox\cs-console\Program.cs @ 28:
003400a4 e87775596d      call    mscorlib_ni+0x867620 (6d8d7620) (System.Diagnostics.Debugger.Break(), mdToken: 0600239a)

C:\dev\sandbox\cs-console\Program.cs @ 29:
>>> 003400a9 5e              pop     esi
003400aa 5d              pop     ebp
003400ab c3              ret

答案 4 :(得分:2)

  

这纯粹是因为.NET运行时识别字符串的值是相同的因为   字符串是不可变的使str2的引用等于str1的引用?

没有。首先,这是因为str1和str2是相同的 - 它们是相同的字符串,因为编译器可以优化它。 strFoo + strFoo是一个编译时间常量,与str1相反。由于字符串在类中是INTERNED,因此它们使用相同的字符串。

其次,字符串OVERRIDES = =方法。查看互联网上可用参考源的源代码一段时间。

答案 5 :(得分:2)

可以覆盖引用相等运算符==;在System.String的情况下,它被覆盖以使用值 - 相等行为。对于真正的引用相等,您可以使用Object.ReferenceEquals()方法,该方法无法覆盖。

答案 6 :(得分:1)

按照代码命中的顺序......

==被覆盖。这意味着,而不是"abc" == "ab" + "c"为引用类型(比较引用而不是值)调用默认==,而是调用string.Equals(a, b)

现在,这将执行以下操作:

  1. 如果这两个确实是相同的参考,则返回true。
  2. 如果其中一个为null,则返回false(如果它们都为null,我们将在上面返回true)。
  3. 如果两者的长度不同,则返回false;
  4. 通过一个字符串执行优化循环,将char-for-char与其余字符串进行比较(实际上是int-for-int,因为内存中有两个整数块,这是所涉及的优化之一)。如果它在没有不匹配的情况下到达终点,则返回true,否则返回false。
  5. 换句话说,它从以下内容开始:

    public static bool ==(string x, string y)
    {
      //step 1:
      if(ReferenceEquals(x, y))
        return true;
      //step 2:
      if(ReferenceEquals(x, null) || ReferenceEquals(y, null))
        return false;
      //step 3;
      int len = x.Length;
      if(len != y.Length)
        return false;
      //step 4:
      for(int i = 0; i != len; ++i)
        if(x[i] != y[i])
          return false;
      return true;
    }
    

    除了步骤4是基于指针的版本,其中展开的循环理想情况下应该更快。我不会表明这一点,因为我想谈谈整体逻辑。

    有明显的捷径。第一步是在步骤1中。由于相等是自反的(身份需要相等,a == a)然后我们可以在纳秒内返回true,即使是一个大小为几MB的字符串,如果与自身相比也是如此。

    第2步不是捷径,因为它必须进行测试,但请注意,因为我们已经为(string)null == (string)null返回了true,所以我们不需要另一个分支。因此,呼叫顺序适合快速结果。

    第3步允许两件事。它既可以在不同长度的字符串上进行快捷方式(总是假的),也意味着在步骤4中,不会意外地拍摄到其中一个字符串的结尾。

    请注意,其他字符串比较不是这种情况,例如WEISSBIERweißbier长度不同,但是大小写不同,因此不区分大小写的比较不能使用步骤3.所有相等比较都可以执行步骤1和2,因为所使用的规则始终有效,所以你应该在你自己使用它们,只有一些可以做第3步。

    因此,虽然你建议它是引用而不是比较的值是错误的,但确实首先将引用作为一个非常重要的捷径进行比较。另请注意,实习字符串(通过编译放置在实习池中的字符串或被调用的string.Intern)将因此经常触发此快捷方式。在您的示例中的代码中就是这种情况,因为编译器在每种情况下都会使用相同的引用。

    如果你知道一个字符串被实习,你可以依赖它(只做参考相等测试),但即使你不确定你可以从中受益(参考相等性测试将至少缩短一些当时)。

    如果你有一堆字符串,你想要经常测试它们中的一些,但你不想像在实习中那样在内存中延长它们的生命周期,那么你可以使用{{3 }}或XmlNameTable(很快将被重命名为ThreadSafeAtomizer,文档移至LockFreeAtomizer - 应该首先以函数而不是实现细节命名。)

    前者由XmlTextReader内部使用,因此由System.Xml的其余部分使用,也可由其他代码使用。我写的后者是因为我想要一个类似的想法,对于并发调用,对于不同的类型,以及我可以覆盖相等比较的地方是安全的。

    在任何一种情况下,如果您将50个不同的字符串全部放入“abc”中,您将获得一个“abc”引用,允许其他人进行垃圾回收。如果你知道这已经发生了,你可以单独依靠ReferenceEquals,如果你不确定,那么在情况确实时你仍然可以从捷径中受益。

答案 7 :(得分:0)

根据msdn(http://msdn.microsoft.com/en-us/library/53k8ybth.aspx):

对于预定义的值类型,如果操作数的值相等,则等于运算符(==)返回true,否则返回false。对于除string之外的引用类型,如果其两个操作数引用同一对象,则==返回true。对于字符串类型,==比较字符串的值。