为什么将两个空字符串相加的结果不是空?

42

当我在C#中搞弄的时候,我遇到了.Net编程中的奇怪行为。

我写了这段代码:

  static void Main(string[] args)
    {
        string xyz = null;
        xyz += xyz;
        TestNullFunc(xyz);
        Console.WriteLine(xyz);

        Console.Read();

    }

    static void TestNullFunc(string abc)
    {
        if (abc == null)
        {
            Console.WriteLine("meow THERE ! ");
        }
        else
        {
            Console.WriteLine("No Meow ");
        }
    }

我得到的输出是No meow,这意味着该字符串不是null。为什么把两个null字符串相加会得到一个非null字符串?

在调试时,当我检查将xyz与自身相加后的值时,其值为""(没有字符)。


1
尝试使用 string.IsNullOrEmpty(string s) :) - qstebom
如果这两个字符串不存在,你就无法将它们连接起来。 - BoltClock
2
@qstebom: 你可以告诉他将空值更改为空字符串。 - BoltClock
2
@BoltClock:取决于他真正想要实现什么。在这种情况下,他可能只是想知道为什么他的代码没有导致NullReferenceException。 - qstebom
10
有趣的事实:我在 C# 编译器工作的第一个任务是重写字符串连接的优化器。如果 M() 返回字符串,你会如何优化 M() + null?编译器生成的代码不是 String.Concat(M(), null) 或者 String.Concat(M(), ""),而是等价于 M() ?? ""。也就是说,使用 M(),但如果它返回 null 就使用 "" - Eric Lippert
显示剩余2条评论
6个回答

67

来自MSDN

在字符串连接操作中,C#编译器将null字符串视为空字符串。

即使xyz是null,在其上调用+=运算符(它被转换为对+运算符 (*) 的调用)也不会抛出NullReferenceException,因为它是一个静态方法。伪代码如下:

xyz = String.+(null, null);

实现将把这个解释为如果它是

xyz = String.+("", "");

(*) C#规范第§7.17.2节:

形如 x op= y 的操作被处理时,会像写成 x op y 一样应用二元运算符重载解析 (§7.3.4)。


7
值得注意的是,操作符是静态方法而非实例方法。 - Daniel A. White
2
@DanielA.White 已添加一条注释,如建议所示。 - dcastro

24

当你使用+=运算符时,实际上是在调用string.Concat方法,如文档所述:

该方法将str0和str1连接起来;它不会添加任何分隔符。 空字符串将替换任何null参数。

事实上,这段代码:

string xyz = null;
xyz += xyz;

将会被编译为:

IL_0000:  ldnull      
IL_0001:  stloc.0     // xyz
IL_0002:  ldloc.0     // xyz
IL_0003:  ldloc.0     // xyz
IL_0004:  call        System.String.Concat
IL_0009:  stloc.0     // xyz

4
要点:实际上,编译器会在极少数情况下省略对String.Concat的调用。 - Eric Lippert

6

如上所述,原因在于连接 null 被视为连接空字符串。

值得考虑的是这种行为的用处。

通常情况下,在二元运算符中有一个操作数是 null 时,我们可以做两件明智的事情:

  1. 结果是 null。
  2. 该操作无效,我们只剩下另一个操作数。

例如,((int?)null) + 3 的结果应该是 null,一般来说这将是最有用的结果,或者我们有意识地防范空值情况(也就是说,我们会添加代码以显式捕获 null 情况)。

但有两个原因不这样做字符串连接。

第一个是要考虑到,由于连接的含义不是算术计算,而是将两个东西粘在一起,那么将 null 粘在某个东西的开头或结尾的最合理结果是什么?很容易说明应该什么都不做,而不是返回 null。

第二个原因是,在实际应用中,当 a + b + c + d 用于字符串时,如果任何一个操作数为 null,我们想要其返回 null 的情况比不想要的情况少得多。

因此,在连接中将 null 视为空字符串是有意义的。由此推断,(string)null + (string)null 的结果是 "",是因为我们没有特殊情况来连接两个 null。

虽然可以添加这种特殊情况,但这样一来,x + "" == x + null 这个属性将不再成立,这可能会导致一些奇怪的情况。


2

try this...

static void TestNullFunc(string abc)
{
    if (string.IsNullOrEmpty( abc))
    {
        Console.WriteLine("meow THERE ! ");
    }
    else
    {
        Console.WriteLine("No Meow ");
    }
}

这个解决方案没有问题,但我要指出它使用的内存比在这里发布的其他解决方案(string.IsNullOrEmpty(abc))更多。 - AStopher
4
“IsNullOrEmpty(abc)”只是“abc == null || abc.Length == 0”的快捷方式,而且很小, JIT编译器会把它内联。那么它会如何使用更多的内存? - Tseng
据一位C#程序员朋友称,任何string函数都会使用更多的内存。这个说法是错误的/无效的吗? - AStopher
4
@zyboxenterprises 是的,这是错误的。IsNullOrEmpty 方法调用时没有额外的内存分配。正如 Tseng 所说,该方法可能会被内联,因此在性能方面甚至不会有任何区别。你的朋友可能是指,在某些罕见情况下(例如性能关键的应用程序),你可以比使用某些字符串方法更好地完成某项任务,例如原地解析字符串而不是使用 Split 等方法。请参阅 Joe Duffy - Beware the string - dcastro
@zyboxenterprises 但即使如此也有争议(请参见evildictaitor在这里的帖子http://channel9.msdn.com/Forums/Coffeehouse/Joe-Duffy--a-managed-system--beat-the-pants-off-all-the-popular-native-programming-environments/d78ab9e23cf741ca9f1ca10f010b4121)。 - dcastro

2

这是因为运算符+=会将Null添加到空字符串中。

所以,编译器将空字符串添加到现有的字符串对象中。

因此,它是空的而不是null。


3
我很肯定它会将空字符串加到空字符串中。 - Cruncher

1

C#从Java中借鉴了它的+运算符的行为。如果+的任一操作数是字符串,则+运算符将调用String.Concat,该方法接受类型Object并连接传递给它的每个非空对象的ToString结果。忽略null引用只是String.Concat操作数不被视为“字符串”的方式的一小部分;这种行为更为明显的一个方面是,不是字符串的类型都会调用它们的ToString方法,无论它们是否本来可以被隐式转换为string


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