由于true
不是字符串类型,字符串如何null + true
?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
这背后的原因是什么?
由于true
不是字符串类型,字符串如何null + true
?
string s = true; //Cannot implicitly convert type 'bool' to 'string'
bool b = null + true; //Cannot implicitly convert type 'string' to 'bool'
这背后的原因是什么?
Answers:
看起来很奇怪,它只是遵循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在这里是空类型-或根本不是一个类型,如果您想这样想的话。它没有提供任何候选人。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文字,因为编译器不知道in 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);
可以只创建一个长度正确的字符串,然后将所有数据复制一次。真好
true
无法转换为string
。如果表达式有效,则类型为string
,但是在这种情况下,未能转换为字符串会使整个表达式出错,因此没有类型。
x
是类型string
。请注意,此处使用的签名是string operator+(string, object)
-它将转换bool
为object
(可以),而不是string
。
原因是因为一旦您介绍了 +
,C#运算符绑定规则就会发挥作用。它将考虑+
可用的运算符集并选择最佳的重载。这些运算符之一是以下内容
string operator +(string x, object y)
此重载与表达式中的参数类型兼容null + true
。因此,它被选择为运算符,并且实质上被评估为((string)null) + true
值"True"
。
C#语言规范的7.7.4节包含有关此分辨率的详细信息。
operator+
进行string
。取而代之的是,它仅存在于编译器的脑海中,并将其简单地转换为对string.Concat
有趣的是,使用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
将被强制转换为null字符串,并且存在从bool到字符串的隐式转换器,因此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