null + true是一个string?

因为true不是一个stringtypes, null + true是一个string?

 string s = true; //Cannot implicitly convert type 'bool' to 'string' bool b = null + true; //Cannot implicitly convert type 'string' to 'bool' 

这背后的原因是什么?

这看起来很奇怪,它只是遵循C#语言规范的规则。

从7.3.4节:

op是一个可重载的二元运算符,x是Xtypes的expression式,y是Ytypes的expression式,其操作forms如下:

  • 确定由X和Y为操作员op(x,y)提供的候选用户定义的操作员集合。 该集由X提供的候选运算符和Y提供的候选运算符的联合组成,每个运算符都使用第7.3.5节的规则确定。 如果X和Y是相同的types,或者如果X和Y是从一个公共基types派生的,那么共享的候选运算符只出现在组合集合中一次。
  • 如果候选用户定义的运算符集合不是空的,那么这成为操作的候选运算符集合。 否则,预定义的二元操作符op实现(包括它们的提升表单)将成为操作的候选操作符集合。 给定运算符的预定义实现在运算符的说明中指定(§7.8至§7.12)。
  • 将第7.5.3节的重载决策规则应用于候选运算符集合,以select参数列表(x,y)的最佳运算符,并且该运算符成为重载parsing过程的结果。 如果重载parsing无法select单个最佳运算符,则会发生绑定时错误。

那么,让我们一起来看看。

X是这里的空types – 或者根本不是types,如果你想这样想的话。 这不是提供任何候选人。 Y是bool ,它不提供任何用户定义的+操作符。 所以第一步找不到用户定义的操作符。

然后编译器转到第二个项目符号点,查看预定义的二元运算符+实现及其提升表单。 这些在规范的第7.8.4节中列出。

如果你查看那些预定义的操作符, 唯一适用的是string operator +(string x, object y) 。 所以候选集有一个单一的条目。 这使得最后的项目符号非常简单…重载决议select该运算符,给出一个整体expression式的stringtypes。

有趣的一点是,即使在未提及的types上有其他用户定义的操作符,也会发生这种情况。 例如:

 // Foo defined Foo operator+(Foo foo, bool b) Foo f = null; Foo g = f + true; 

这很好,但它不用于null文字,因为编译器不知道在Foo查找。 它只知道考虑string因为它是在规范中明确列出的预定义运算符。 (事实上​​,它不是由stringtypes定义的运算符… 1 )这意味着这将无法编译:

 // Error: Cannot implicitly convert type 'string' to 'Foo' Foo f = null + true; 

其他的第二操作数types当然会使用一些其他的操作符:

 var x = null + 0; // x is Nullable<int> var y = null + 0L; // y is Nullable<long> var z = null + DayOfWeek.Sunday; // z is Nullable<DayOfWeek> 

1您可能想知道为什么没有string+运算符。 这是一个合理的问题,我只是在猜测答案,但考虑这个expression式:

 string x = a + b + c + d; 

如果string在C#编译器中没有特殊的shell,这将最终有效:

 string tmp0 = (a + b); string tmp1 = tmp0 + c; string x = tmp1 + d; 

所以这是创build两个不必要的中间string。 但是,因为在编译器中有特殊的支持,它实际上能够将上面的代码编译为:

 string x = string.Concat(a, b, c, d); 

它可以创build一个正确长度的单个string,将所有数据完全复制一次。 尼斯。

之所以是因为一旦你介绍了+那么C#操作符绑定规则就会起作用。 它将考虑可用的一组+运营商,并select最佳的过载。 其中一个运营商是以下

 string operator +(string x, object y) 

此重载与expression式null + true的参数types兼容。 因此,它被选作操作符,并且基本上被评估为((string)null) + true ,其值为"True"

C#语言规范的7.7.4节包含了这个分辨率的细节。

编译器出去搜寻一个运算符+(),它可以首先接受一个空参数。 标准值types没有任何限定,null对于它们来说不是有效的值。 唯一的匹配是System.String.operator +(),没有歧义。

该运算符的第二个参数也是一个string。 那kapooey,不能隐式地将bool转换为string。

有趣的是,使用Reflector来检查生成的内容,下面的代码:

 string b = null + true; Console.WriteLine(b); 

由编译器转换成这个:

 Console.WriteLine(true); 

这个“优化”背后的原因有点奇怪,我不得不说。

另外,下面的代码:

 var b = null + true; var sb = new StringBuilder(b); 

被转换成

 string b = true; StringBuilder sb = new StringBuilder(b); 

其中string b = true; 实际上是不被编译器接受的。

null将被转换为空string,并且存在从bool到string的隐式转换器,因此true将被转换为string,然后,将应用+运算符:它就像:string str =“”+ true.ToString();

如果你用Ildasm检查它:

string str = null + true;

它如下所示:

 .locals init ([0] string str) IL_0000: nop IL_0001: ldc.i4.1 IL_0002: box [mscorlib]System.Boolean IL_0007: call string [mscorlib]System.String::Concat(object) IL_000c: stloc.0 
 var b = (null + DateTime.Now); // String var b = (null + 1); // System.Nullable<Int32> | same with System.Single, System.Double, System.Decimal, System.TimeSpan etc var b = (null + new Object()); // String | same with any ref type 

疯?? 不,这一定是有原因的。

有人打电话给Eric Lippert

这是为了方便(连接string是一个常见的任务)。

正如BoltClock所说,“+”运算符是在数字types,string上定义的,也可以为我们自己的types定义(运算符重载)。

如果参数types中没有重载的“+”运算符,并且它们不是数字types,则编译器将缺省为字符​​串连接。

编译器在使用“+”连接时插入对String.Concat(...)调用,Concat的实现在每个传入的对象上调用ToString。