由于true
不是字符串类型,null + true
如何成为字符串?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
这背后的原因是什么?
答案 0 :(得分:149)
这看起来很奇怪,它只是遵循C#语言规范中的规则。
从第7.3.4节:
x op y形式的一个操作,其中op是一个可重载的二元运算符,x是一个X类型的表达式,y是一个Y类型的表达式,按如下方式处理:
- 确定由X和Y为操作运算符op(x,y)提供的候选用户定义运算符集。该集合由X提供的候选运算符和Y提供的候选运算符组合而成,每个运算符使用§7.3.5的规则确定。如果X和Y是相同的类型,或者X和Y是从公共基类型派生的,那么共享候选运算符只出现在组合集中一次。
- 如果候选用户定义的运算符集合不为空,则这将成为该操作的候选运算符集。否则,预定义的二元运算符op实现(包括它们的提升形式)将成为该操作的候选运算符集。给定运算符的预定义实现在运算符的描述中指定(§7.8到§7.12)。
- §7.5.3的重载决策规则应用于候选运算符集合,以根据参数列表(x,y)选择最佳运算符,并且此运算符成为重载解析过程的结果。如果重载决策无法选择单个最佳运算符,则会发生绑定时错误。
所以,让我们依次介绍一下。
X是这里的null类型 - 或者根本不是类型,如果你想这样想的话。它没有提供任何候选人。 Y是bool
,它不提供任何用户定义的+
运算符。所以第一步找不到用户定义的运算符。
然后编译器转到第二个项目符号点,查看预定义的二元运算符+实现及其提升的表单。这些列在规范的第7.8.4节中。
如果查看这些预定义的运算符,则仅适用的运算符为string operator +(string x, object y)
。所以候选集有一个条目。这使得最后一个要点非常简单......重载决策选择该运算符,使整体表达式类型为string
。
一个有趣的观点是,即使在未提及的类型上有其他用户定义的运算符,也会发生这种情况。例如:
// Foo defined Foo operator+(Foo foo, bool b)
Foo f = null;
Foo g = f + true;
没关系,但它不用于null文字,因为编译器不知道要查看Foo
。它只知道考虑string
,因为它是规范中明确列出的预定义运算符。 (事实上,不是由字符串类型定义的运算符... 1 )这意味着这将无法编译:
// Error: Cannot implicitly convert type 'string' to 'Foo'
Foo f = null + true;
其他第二操作数类型当然会使用其他一些操作符:
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 x = a + b + c + d;
如果string
在C#编译器中没有特殊外壳,那么最终会有效:
string tmp0 = (a + b);
string tmp1 = tmp0 + c;
string x = tmp1 + d;
因此创建了两个不必要的中间字符串。但是,因为编译器中有特殊的支持,所以实际能够将上面的代码编译为:
string x = string.Concat(a, b, c, d);
只能创建一个完全正确长度的单个字符串,只复制一次所有数据。好的。
答案 1 :(得分:44)
原因是因为一旦你引入了+
,那么C#运算符绑定规则就会发挥作用。它将考虑可用的+
运算符集并选择最佳重载。其中一个运营商是以下
string operator +(string x, object y)
此重载与表达式null + true
中的参数类型兼容。因此,它被选为运算符,并且基本上被评估为((string)null) + true
,其值为"True"
。
C#语言规范的第7.7.4节包含有关此分辨率的详细信息。
答案 2 :(得分:11)
编译器会寻找一个可以先取空参数的运算符+()。没有标准值类型限定,null不是它们的有效值。唯一匹配的是System.String.operator +(),没有歧义。
该运算符的第二个参数也是一个字符串。那就是kapooey,不能隐含地将bool转换为字符串。
答案 3 :(得分:10)
有趣的是,使用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;
。
答案 4 :(得分:8)
null
将被转换为空字符串,并且存在从bool到string的隐式转换器,因此true
将被转换为字符串,然后,+
运算符将被应用:它是喜欢: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
答案 5 :(得分:5)
这样做的原因是方便(连接字符串是一项常见任务)。
正如BoltClock所说,'+'运算符是在数字类型,字符串上定义的,也可以为我们自己的类型定义(运算符重载)。
如果参数的类型上没有重载的'+'运算符且它们不是数字类型,则编译器默认为字符串连接。
当您使用'+'连接时,编译器会插入对String.Concat(...)
的调用,并且Concat的实现会在传入其中的每个对象上调用ToString。
答案 6 :(得分:5)
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
......