为什么这两个字符串比较返回不同的结果?

35

这里有一小段代码:

String a = "abc";

Console.WriteLine(((object)a) == ("ab" + "c")); // true 
Console.WriteLine(((object)a) == ("ab" + 'c')); // false 

为什么?


"ab" + 'c' 是两种不同的对象类型,它的结果为 ab - AStopher
8
当你把一个字符串和一个字符相加时,结果是字符串 "abc"。 - Lasse V. Karlsen
11
请参考这篇文章http://ericlippert.com/2009/09/28/string-interning-and-string-empty/,了解与你所注意到的现象密切相关的一些问题。 - Eric Lippert
2个回答

75

因为==运算符是进行引用比较。在C#编译器中,所有在编译时已知的“相等”的字符串都被“分组”在一起,所以

string a = "abc";
string b = "abc";

将指向同一“abc”字符串。因此,它们是引用相等的。

现在,("ab" + "c") 在编译时被简化为 "abc",而"ab" + 'c'则不是,因此不是引用相等的(连接操作在运行时完成)。

请点击这里查看反编译的代码。

我要补充说明的是,Try Roslyn 进行了错误的反编译 :-),甚至 IlSpy 也是如此 :-(

它反编译为:

string expr_05 = "abc"
Console.WriteLine(expr_05 == "abc");
Console.WriteLine(expr_05 == "ab" + 'c');

所以是字符串比较。但至少可以清楚地看到一些字符串在编译时计算出来。

为什么您的代码要执行引用比较?因为您将其中一个成员强制转换为 object ,并且.NET中的 operator == 不是 virtual ,因此必须使用编译器具有的信息在编译时解决它,然后... 取自==运算符

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

对于编译器而言, == 运算符的第一个操作数不是 string (因为您进行了强制转换),因此它不属于 string 比较。

有趣的事实:在.NET的CIL级别(汇编语言),使用的操作码是 ceq ,它对基元值类型进行值比较,并对引用类型进行引用比较(因此最终始终进行位比较,对于带NaN的浮点类型有一些例外)。 它不使用“特殊”的 operator == 方法。 这可以在此示例中看到。

其中

Console.WriteLine(a == ("ab" + 'c')); // True 

在调用时在编译时解析

call bool [mscorlib]System.String::op_Equality(string, string)

而其他的==则很简单

ceq
这解释了为什么Roslyn反编译器表现得“不好”(如IlSpy :(,参见错误报告)... 它看到一个操作码ceq,没有检查是否需要强制转换来重建正确的比较。

Holger问为什么只有两个字符串字面值之间的加法是由编译器完成的... 现在,以非常严格的方式阅读C# 5.0规范,并将C# 5.0规范视为与.NET规范“分开的”(除了C# 5.0对某些类/结构/方法/属性的先决条件等异常情况),我们有:

字符串连接:

string operator +(string x, string y);
string operator +(string x, object y);
string operator +(object x, string y);
这些二元操作符的重载执行字符串连接。如果字符串连接的操作数为null,则替换为空字符串。否则,通过调用从类型对象继承的虚拟 ToString 方法将任何非字符串参数转换为其字符串表示形式。如果 ToString 返回 null,则替换为空字符串。因此,“string + string”、“string + null”、“null + string”的情况都被准确描述,并且它们的结果可以仅使用C#规范的规则“计算”。对于每种其他类型,必须调用虚拟 ToString 方法。在 C# 规范中,任何类型的 virtual ToString 方法的结果都未定义,因此如果编译器“假定”它的结果,它会做错事。例如,一个返回 Yes/No 而不是 True/False 的 System.Boolean.ToString() 版本仍然适用于 C# 规范。

3
您应该提及为什么还需要进行引用比较,以使这个答案更完美。 - Lasse V. Karlsen
可能有用的是提到,如果将 a 转换为 string,那么两个控制台输出都是 true - Kaz
3
剩下的唯一问题是为什么 "ab"+"c" 在编译时就被解决了,而 "ab"+'c' 却没有。例如在Java中,两者都是编译时常量,所以为什么在C#中不行... - Holger
@Holger 他们可能没有考虑到这一点。从我做的一些测试来看,我认为他们只优化了 string + nullstring + stringstring + int 没有被优化。string + bool 也没有。请注意,String.Empty 是一个只读字段,如果使用它,它也不会被优化... "A" + String.Empty 是在运行时完成的。 - xanatos

-3

地址不同。如果您想比较字符串字符,请建议使用equals。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接