关于C#:空字符串作为特殊情况?

Empty string as a special case?

我读了乔恩·斯基特的测验,我想知道为什么我的第二个样本不起作用,而第一个样本起作用。

为什么会产生true

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

但这一条并没有:

1
2
3
4
5
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

我用的是fw4.5和vs2010。

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

enter image description here


下面是EricLippert的一篇博客文章,它回答了你的问题:string interning和string.empty。

他描述了类似的情况:

1
2
3
4
5
6
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实例,即使是实习的时候。默认情况下,只有编译时的文本是内部的。这意味着以下代码打印正确:

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

但是,如果您试图在运行时以编程方式创建带有"k"内容的字符串,例如使用string(char[])构造函数、在对象上调用ToString()、使用StringBuilder等,则默认情况下不会得到interned字符串。这张印的是假的;

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

为什么?因为在运行时插入字符串是昂贵的。

There Ain't No Such Thing As A Free Lunch.

(...)

In short, it is in the general case not worth it to intern all strings.

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

Some versions of the .NET runtime automatically intern the empty string at runtime, some do not!


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

1
2
3
4
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的字符串都是空字符串,并且与任何其他空字符串相同——所有其他字符串都要求我们查看字符而不仅仅是长度)。


第一种情况比较两个对同一对象的引用(String.Empty)。对2个object变量调用operator==会引起它们的引用比较,并给出true的结果。

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

如果在第二种情况下,将xy类型赋给string类型,将调用string.operator==覆盖,比较则得出true类型。

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

Marcinaraszek发布的答案参考了Lippert的博客,该博客讨论了字符串实习。那个博客文章讨论了字符串类的其他用法。从前面提到的Lippert的博客中可以看到这个例子:

1
2
3
4
5
6
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:

1
2
3
4
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_线。无论文本是否被截取,ToArray()方法都会生成一个新的空数组,而String::.ctor(char[])方法会给出String.Empty

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


我的假设是为什么第一个得出正确而第二个得出错误:

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

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

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


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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);

结果

1
2
3
True
False
True

我想这就是我引用乔恩·斯基特答案的原因关于字符串比较

string.equals()和==运算符是否真的相同?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        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();

产量

1
2
3
4
5
6
False
Address x1:0x2613E5
Address y1:0x2613E5
False
Address x:0x2613E5
Address y:0x2613E5


根据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

这很可能为您提供了一种创建一致的比较方法,尽管正如@benn建议的那样,您更愿意显式地使用intern函数。

考虑到拳击,你也可以用string.Equals代替==