为什么连接空字符串有效但不调用“ null.ToString()”有效吗?


126

这是有效的C#代码

var bob = "abc" + null + null + null + "123";  // abc123

这是无效的C#代码

var wtf = null.ToString(); // compiler error

为什么第一个陈述有效?


97
我觉得null.ToString()给你起个名字很特别wtf。为什么会让您感到惊讶?当您一无所有要调用实例方法时,就无法调用它。
BoltClock

11
@BoltClock:当然可以在空实例上调用实例方法。在C#中是不可能的,但是在CLR上却非常有效:)
嬉皮士2012年

1
关于String.Concat的答案几乎是正确的。实际上,问题中的特定示例是常量折叠中的一个,并且第一行中的空值被编译器消除-即它们永远不会在运行时求值,因为它们不再存在-编译器已将其擦除。我在这里写了一些关于字符串连接和不断折叠的所有规则的独白,以获取更多信息:stackoverflow.com/questions/9132338/…
克里斯·沙恩

77
您可以在字符串中添加任何内容,但不能一无所有。
RGB

第二条陈述无效吗?class null_extension { String ToString( Object this arg ) { return ToString(arg); } }
ctrl-alt-delor 2012年

Answers:


163

第一个工作的原因:

MSDN

在字符串连接操作中,C#编译器将空字符串与空字符串相同,但是不会转换原始空字符串的值。

有关+二进制运算符的更多信息:

当一个或两个操作数的类型为string时,binary +运算符将执行字符串连接。

如果字符串连接的操作数为null,则替换为空字符串。否则,通过调用ToString从类型对象继承的虚拟方法,任何非字符串参数都将转换为其字符串表示形式。

如果ToString返回null,则替换为空字符串。

秒内出错的原因是:

null(C#参考) -null关键字是一种表示空引用的文字,它不引用任何对象。null是引用类型变量的默认值。


1
这样可以吗?var wtf = ((String)null).ToString();自从我使用C#以来,我最近在Java中工作,可以转换为null。
Jochem 2012年

14
@Jochem:您仍在尝试在空对象上调用方法,所以我猜没有。把它看成是null.ToString()VS ToString(null)
Svish 2012年

@Svish是的,现在我再次想到它,它是一个空对象,所以您是对的,它将无法工作。在Java中也不会:空指针异常。没关系。Tnx的回复![编辑:在Java中测试了它:NullPointerException。区别在于使用强制转换可以编译,没有强制转换则不能编译]。
约赫姆2012年

通常,没有理由故意合并或ToString null关键字。如果您需要一个空字符串,请使用string.Empty。如果需要检查字符串变量是否为null,则可以使用(myString == null)或string.IsNullOrEmpty(myString)。或者将null字符串变量转换为string.Empty,请使用myNewString =(myString == null ?? string.Empty)
csauve 2012年

@Jochem进行强制转换将使其编译,但实际上会在运行时抛出NullReferenceException
jeroenh 2012年

108

因为+C#中的运算符在内部转换为String.Concat,这是一种静态方法。而且这种方法碰巧null像一个空字符串一样对待。如果您String.Concat在Reflector中查看源代码,您将看到它:

// while looping through the parameters
strArray[i] = (str == null) ? Empty : str;
// then concatenate that string array

(MSDN也提到了它:http : //msdn.microsoft.com/zh-cn/library/k9c94ey1.aspx

另一方面,ToString()是无法调用的实例方法null(应将哪种类型用于null?)。


2
但是在String类中没有+运算符。.那么编译器是否可以转换为String.Concat?
超级逻辑2012年

@superologic是的,这就是编译器所做的。因此,实际上,+C#中字符串的运算符只是的语法糖String.Concat
Botz3000

除此之外,编译器会自动选择最有意义的Concat重载。可用的重载包括1、2或3个对象参数,4个对象参数+ __arglist和一个params对象数组版本。
Wormbo,2012年

在那里修改了什么字符串数组?是否Concat在给定非空字符串数组的情况下始终创建一个新的非空字符串数组,还是发生了其他情况?
2015年

@supercat修改后的数组是一个新数组,然后将其传递给私有帮助器方法。您可以使用反射器自己查看代码。
Botz3000,2015年

29

一个样本将被翻译为:

var bob = String.Concat("abc123", null, null, null, "abs123");

Concat方法检查输入并将null转换为空字符串

所述第二样品将被翻译成:

var wtf = ((object)null).ToString();

所以null这里会产生一个参考异常


实际上,AccessViolationException将抛出:)
嬉皮士2012年

我刚刚做过:) ((object)null).ToString()=> AccessViolation ((object)1 as string).ToString()=>NullReference
嬉皮士

1
我有NullReferenceException。DN 4.0
Viacheslav Smityukh,2012年

有趣。当我把((object)null).ToString()里面try/catch我得到NullReferenceException了。
嬉皮

3
@Ieppie,不同之处在于它也不会抛出AccessViolation(为什么?)-NullReferenceException。
jeroenh 2012年

11

您的代码的第一部分就像在中一样String.Concat

添加字符串时,C#编译器将调用该函数。” abc" + null翻译为String.Concat("abc", null)

在内部,该方法替换nullString.Empty。因此,这就是代码的第一部分不引发任何异常的原因。就像

var bob = "abc" + string.Empty + string.Empty + string.Empty + "123";  //abc123

在代码的第二部分中,由于'null'不是对象,因此引发异常,null关键字是一个表示null引用的文字,它不引用任何对象。null是引用类型变量的默认值。

ToString()”是可以由对象实例而不是任何文字调用的方法。


3
String.Empty不等于null。在某些方法中,它只是以相同的方式处理String.Concat。例如,如果您将一个字符串变量设置为nullString.Empty则尝试在其上调用方法时,C#不会将其替换为。
Botz3000

3
几乎。null不会像String.EmptyC#那样被对待,只是像中的一样对待String.Concat,这就是C#编译器在添加字符串时调用的内容。"abc" + null转换为String.Concat("abc", null),并在内部将该方法替换nullString.Empty。第二部分是完全正确的。
Botz3000

9

在.net之前的COM框架中,任何接收到字符串的例程都必须在完成后释放它。由于将空字符串传入和传出例程非常普遍,并且由于尝试“释放”空指针被定义为合法的不执行操作,因此Microsoft决定使用空字符串指针来表示空字符串。

为了与COM兼容,.net中的许多例程会将空对象解释为合法表示形式的空字符串。通过对.net及其语言进行一些细微更改(最显着的是允许实例成员指示“不要以虚拟方式调用”),Microsoft可以使null声明为String类型的对象的行为更像是空字符串。如果Microsoft这样做了,那么它也将不得不做Nullable<T>一些不同的工作(以便允许- Nullable<String>无论如何恕我直言,他们应该做些什么)和/或定义一个NullableString类型,该类型基本上可以与互换String,但不会将其视为null作为有效的空字符串。

实际上,在某些情况下,a null将被视为合法的空字符串,而在另一些情况下则不会。这不是一个非常有用的情况,而是程序员应该注意的情况。通常,stringValue.someMember如果stringValueis为null,则形式的表达式将失败,但是大多数将字符串作为参数的框架方法和运算符都将null视为空字符串。


5

'+'是中缀运算符。像任何运算符一样,它实际上是在调用方法。您可以想象一个非中缀版本"wow".Plus(null) == "wow"

实施者已经决定了类似的事情...

class String
{
  ...
  String Plus(ending)
  {
     if(ending == null) return this;
     ...
  }
} 

所以..你的例子变成

var bob = "abc".Plus(null).Plus(null).Plus(null).Plus("123");  // abc123

这与

var bob = "abc".Plus("123");  // abc123

null绝对不会成为字符串。所以null.ToString()没什么不同 null.VoteMyAnswer()。;)


确实,它更像是var bob = Plus(Plus(Plus(Plus("abc",null),null),null),"123");。运算符重载是静态方法,其核心是:msdn.microsoft.com/en-us/library/s53ehcz3(v=VS.71).aspx是不是,var bob = null + "abc";或者特别string bob = null + null;是无效的。
Ben Mosher 2012年

你是对的,我只是觉得如果我这样解释我会太困惑了。
奈杰尔·索恩


3

有人在此讨论线程中说,您不能一无所有。 (我认为这是一个很好的短语)。但是可以-您可以 :-),如以下示例所示:

var x = null + (string)null;     
var wtf = x.ToString();

工作正常,根本不引发异常。唯一的区别是您需要将一个空值强制转换为字符串-如果删除(string)强制转换,则示例仍会编译,但会引发运行时异常:“运算符'+'在操作数的键入“ <null>”和“ <null>””。

注意:在上面的代码示例中,x的值不像您期望的那样为null,在将一个操作数转换为字符串后,它实际上是一个空字符串。


另一个有趣的事实是,在C#/ .NET中null如果您考虑不同的数据类型,则对待方式并不总是相同的。例如:

int? x = 1;  //  string x = "1";
x = x + null + null;
Console.WriteLine((x==null) ? "<null>" : x.ToString());

关于代码段的第一行:如果xint?包含value 的可为空的整数变量(即)1,那么您将获得结果<null>。如果它是一个带有value的字符串(如注释中所示)"1",那么您将"1"返回而不是<null>

NB也很有趣:如果您使用var x = 1;的是第一行,那么您会遇到运行时错误。为什么?因为赋值会将变量x转换为数据类型int,该数据类型不能为空。编译器不在int?此处假设,因此在null添加第二行时失败。


2

null简单地忽略添加到字符串。null(在第二个示例中)不是任何对象的实例,因此它甚至没有ToString()方法。这只是一个字面意思。


1

因为连接字符串string.Emptynull何时连接字符串没有区别。您也可以将null传递给string.Format。但是,您尝试在上调用方法null,该方法将始终导致NullReferenceException并因此生成编译器错误。
如果出于某种原因您确实想这样做,则可以编写一个扩展方法,先检查null然后返回string.Empty。但是仅在绝对必要时(我认为),才应使用此类扩展名。


0

通常:根据规范,接受null作为参数可能有效也可能无效,但是在null上调用方法始终无效。


这就是为什么在字符串的情况下+运算符的操作数可以为null的原因。这有点VB的事情(对不起的家伙),它使程序员的生活更轻松,或者假定程序员不能处理null。我完全不同意此规范。'未知'+'任何东西'应该仍然是'未知'...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.