为什么 "hello" + + '/' + "world" 等于 "hello47world"?

48

对于这个 C#,a==true

bool a = "hello" +   '/' + "world" == "hello/world";

对于这个 C# 代码,b==true 为真:

bool b = "hello" + + '/' + "world" == "hello47world";
我想知道这是如何发生的,更重要的是,为什么C#语言的架构师选择了这种行为?

28
这个讨论串中,大家说:"+是一种一元操作符,将char转换为int类型"。但没有人解释为什么会这样做。 - Rawling
1
@fgp 我有一篇关于为什么特别使用 int 的文章,但我怀疑没有人在意 :) - Rawling
1
@Rawling 给我点启示吧 ;-) - fgp
1
@fgp 好了,就是这样。这是令人兴奋的事情。 - Rawling
1
@leftaroundabout 不,从这个意义上讲,它类似于C、C++JavaJavascript,因为它们都使用一元+将字符提升为整数。 - phuclv
显示剩余3条评论
6个回答

50
第二个+char转换为int,并将其添加到字符串中。字符/的ASCII值为47,然后由另一个+运算符转换为字符串。
斜杠前面的+运算符隐式地将其转换为int类型。请参见MSDN上的+操作符,查看“一元加”部分。

数值类型的一元+操作符的结果只是这个操作数的值。

实际上,我通过查看+运算符实际调用了什么来弄清楚这一点。(我想这是ReSharper或VS 2015的功能) enter image description here enter image description here

3
很棒,Resharper又一次让我惊叹不已!加油JetBrain团队! - Tomer W
2
“因为加号在斜杠之前,所以返回的是增加之前的值。” <- 这个表达式既没有前置也没有后置递增运算符 ++,那你为什么要提到它们呢?这里的 + 是一元 + 运算符,在整数上的作用是恒等。 - CodesInChaos
1
这可能是我正在使用的VS 2015的一个功能。(也适用于Resharper 9) - Cyral
4
没有Re,但对我仍然有效,因此这是VS 2015。 - Dzienny
1
是的。只需检查C#内置的转换和运算符即可。在C#中,operator +有许多内置重载。这里相关的二进制重载是string operator +(string x, object y),它调用object参数y上的ToString(),并连接两个字符串。对于一元重载,重要的是要注意没有一元重载接受char。但是,char可以隐式转换为ushort和“更大”的整数类型。其中包括int,而+'/'的最佳重载是这个int operator +(int x) - Jeppe Stig Nielsen
显示剩余5条评论

26

这是因为你正在使用一元运算符+。它类似于一元运算符-,但它不改变操作数的符号,所以它在这里的唯一效果是将字符'/'隐式转换为int

+'/'的值是/的字符代码,即47。

代码执行的内容与以下代码相同:

bool b = "hello" + (int)'/' + "world" == "hello47world";

但我想知道让一元运算符+char上定义的动机是什么。我可能会将其作为编译时错误。如果有人需要数值,为什么不明确地使用转换?(所以,基本上,为什么char具有对int的_隐式_转换?) - Vlad
@Vlad:Eric Lippert写了一些关于这个问题的内容:http://blogs.msdn.com/b/ericlippert/archive/2009/10/01/why-does-char-convert-implicitly-to-ushort-but-not-vice-versa.aspx - Guffa
@Guffa:文本的匹配部分是“在C编程中,将字符视为整数的传统非常悠久——可以获得它们的基础值,或对它们进行数学运算。”,我会把它理解为“C时代的遗产,当时一切都是数字”。现在,随着非英语环境(因此有效字母不再是一个范围)和Unicode及其怪癖(因此大写不再是位操作,两个字符在一起不一定产生两个字符的字符串),使用数字字符值的想法越来越不具吸引力。 - Vlad
1
@Vlad:是的,那是真的。允许隐式转换的动机在于它不会像从数字到字符的隐式转换那样有害。OP发现了三种特殊情况的组合,共同产生了意外的结果;很少使用的一元正号运算符引起了隐式转换,字符到整数的隐式转换以及字符串和整数的连接。如果其中任何一个需要明确指定,则代码将无法按照这种方式工作。 - Guffa

16

你也许会问,为什么要专门针对 char 类型使用运算符 int operator +(int x) 而不是其他很多优秀的一元 + 运算符呢?点击这里了解更多。

  • 一元运算符重载规则指定首先查找用户定义的一元运算符,但由于char没有这些内容,编译器会查找预定义的一元+操作符。
  • 显然这些操作符都不接受char,因此编译器使用重载决策规则来确定哪个操作符(intuintlongulongfloatdouble decimal)是最佳的。
  • 这些决策规则指定查找哪个函数最佳……这基本上意味着查找哪个参数类型从char提供了最佳转换
  • int胜过longfloatdouble,因为您可以隐式转换int到这些类型,而不能反向转换。
  • int胜过uintulong,因为……最佳转换规则说它胜出。

1
太棒了,这解释了为什么C#的架构师设计它以这种方式工作。谢谢! - Contango

2
这是一个隐式转换("char可以隐式转换为ushort、int、uint、long、ulong、float、double或decimal。" (charMSDN))。如下是最简单的复现形式:
int slash = +'/'; // 47

Char在内部是一个结构体。"用途:这是表示Unicode字符的值类"(char.csms referencesource),而结构体之所以可以隐式转换是因为它实现了IConvertible接口。
public struct Char : IComparable, IConvertible

具体而言,使用这段代码。
/// <internalonly/>
int IConvertible.ToInt32(IFormatProvider provider) {
    return Convert.ToInt32(m_value);
}

IConvertible”接口在代码注释中说明:

// IConvertible接口表示包含值的对象。此接口由System命名空间中的以下类型实现:
// Boolean、Char、SByte、Byte、Int16、UInt16、Int32、UInt32、Int64、UInt64、
// Single、Double、Decimal、DateTime、TimeSpan和String。

回顾一下结构体的目的(作为Unicode字符的值代表),很明显语言中这种行为的意图是为了提供一种将该值转换为支持的类型的方式。 “IConvertible”继续说明:

// System.XXX值类提供的IConvertible实现仅简单地转发到适当的Value.ToXXX(YYY)方法(下面描述了Value类)。
// 在不存在Value.ToXXX(YYY)方法的情况下(因为不支持特定的转换),IConvertible实现应该只抛出InvalidCastException异常。

这段文字明确说明不支持的转换会抛出异常。同时也明确说明将字符转换为整数将给出该字符的整数值。
ToInt32(Char) 方法返回一个32位有符号整数,表示值参数的UTF-16编码代码单元。总的来说,这种行为的原因似乎是不言自明的。字符的整数值作为“UTF-16编码代码单元”具有意义。反斜杠的值为47。
由于存在值转换并且char是内置的数字类型,所以加号隐式地从编译时转换为整数。在一个小程序中可以看到上面简单示例的重用(linqpad可用于测试)。
void Main()
{
    int slash = +'/';
    Console.WriteLine(slash);
}

变成
IL_0000:  ldc.i4.s    2F 
IL_0002:  stloc.0     // slash2
IL_0003:  ldloc.0     // slash2
IL_0004:  call        System.Console.WriteLine
IL_0009:  ret    

其中 '/' 只需转换为十六进制值2F(十进制为47),然后从那里使用。


1
+ '/' 

这段文字是关于字符"/"的UTF-16 (decimal) 47字符编码,@Guffa已经解释了其中的原因。


感谢 @Guffa 指出这个错误。在 .NET 中,C# 使用 UTF-16 作为字符串的默认编码。 - a45b
2
@Guffa 有趣...如果你有 +'',会发生什么?它会导致语法错误吗? - user824425
2
@Rhymoid 这会导致一个错误。"字符字面量中的字符太多"。 - Cyral
4
@Cyral 很有道理。对于那些想知道的人:该表情符号位于SMP中,因此UTF-16使用代理对进行编码(在本例中为U+D83C U+DF81),编译器将其计为“两个字符”(这与事实相差甚远)。 - user824425

1
在C#中,char用单引号表示,例如在你的情况下是'/',char前面的+运算符充当一元运算符,并要求编译器提供字符'/'的UTF值,即47。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接