null + true字符串如何?


112

由于true不是字符串类型,字符串如何null + true

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

这背后的原因是什么?


27
您执行了没有意义的操作,然后不喜欢编译器生成的消息?这是无效的C#代码...您到底想要它做什么?
霍根2010年

19
@霍根:代码似乎是随机的和学术性的,可以出于纯粹的好奇心提出这个问题。我的猜测...
BoltClock

8
null + true在JavaScript中返回1。
法提赫·阿塞特

Answers:


147

看起来很奇怪,它只是遵循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;

如果stringC#编译器中没有特殊字符,则最终效果将如下:

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

这样就创建了两个不必要的中间字符串。但是,由于编译器内有特殊支持,因此它实际上可以将以上代码编译为:

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

可以只创建一个长度正确的字符串,然后将所有数据复制一次。真好


我完全不同意“给出字符串的整体表达式类型”。错误来自true无法转换为string。如果表达式有效,则类型为string,但是在这种情况下,未能转换为字符串会使整个表达式出错,因此没有类型。
leppie 2010年

6
@leppie:表达式有效的,并且其类型是字符串。尝试“ var x = null + true;” -它可以编译并且x是类型string。请注意,此处使用的签名是string operator+(string, object)-它将转换boolobject(可以),而不是string
乔恩·斯基特

谢谢乔恩,现在明白了。规范版本2中的BTW第14.7.4节。
leppie 2010年

@leppie:ECMA编号中有吗?总是有很大不同:(
乔恩·斯基特

47
在20分钟内。他在20分钟内写下了整件事。他应该写书或某物……等等。
Epaga 2010年

44

原因是因为一旦您介绍了 +,C#运算符绑定规则就会发挥作用。它将考虑+可用的运算符集并选择最佳的重载。这些运算符之一是以下内容

string operator +(string x, object y)

此重载与表达式中的参数类型兼容null + true。因此,它被选择为运算符,并且实质上被评估为((string)null) + true"True"

C#语言规范的7.7.4节包含有关此分辨率的详细信息。


1
我注意到生成的IL直接用于调用Concat。我当时以为会在其中看到对+运算符函数的调用。这仅仅是内联优化吗?
quentin-starin 2010年

1
@qstarin我认为主要原因是,有没有真正operator+进行string。取而代之的是,它仅存在于编译器的脑海中,并将其简单地转换为对string.Concat
JaredPar的

1
@qstarin @JaredPar:我在回答中对此进行了更多介绍。
乔恩·斯基特

11

编译器开始寻找可以先使用null参数的operator +()。没有一个标准值类型符合条件,null对其无效。唯一匹配的是System.String.operator +(),没有歧义。

该运算符的第二个参数也是一个字符串。那就是kapooey,无法将bool隐式转换为字符串。


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;实际上没有编译器所接受。


8

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

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...


5

这样做的原因是方便(连接字符串是常见的任务)。

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

如果参数的类型没有重载的“ +”运算符,并且它们不是数字类型,则编译器默认为字符串连接。

String.Concat(...)当您使用'+'进行连接时,编译器将插入对的调用,并且Concat的实现对传递给它的每个对象调用ToString。

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.