为什么C#的二进制运算符无论输入格式如何,始终返回int类型?

24
如果我有两个 byte 类型的变量 ab,为什么会出现以下情况:
byte c = a & b;

如果我在ab的前面加上显式转换,它会产生有关将字节转换为整数的编译器错误吗?

此外,我知道关于这个问题,但我不确定它如何适用于这里。 这似乎是关于operator &(byte operand, byte operand2)的返回类型的问题,编译器应该像处理其他运算符一样进行排序。


1
请查看 https://dev59.com/U0jSa4cB1Zd3GeqPHrIB 或 https://dev59.com/DnNA5IYBdhLWcg3whuV_ 或 https://dev59.com/n3NA5IYBdhLWcg3wfN6O,你可能会找到答案... - Joey
可能是重复的问题:byte + byte = int... why? - nawfal
4个回答

27
为什么C#的位运算符始终返回int类型,而不考虑其输入的格式?
我不同意“始终”的说法。下面这个示例可以工作,并且 a & b 的结果类型是 long
long a = 0xffffffffffff;
long b = 0xffffffffffff;
long x = a & b;
如果一个或两个参数是longulonguint类型,则返回类型不是int

C# 的按位运算符为什么会在输入为 byte 时返回 int 呢?

byte & byte的结果是int,因为byte上没有定义&运算符。(来源

int具有&运算符,并且还有从byteint的隐式转换,所以当您编写byte1 & byte2时,这实际上等同于编写((int) byte1) & ((int) byte2),其结果是一个int


1
是的,但这并不能解释为什么结果是 int - Philippe Leybaert
@Philippe Leybaert: 添加了额外段落。 - Mark Byers
2
什么?它清楚明确地解释了为什么结果是int。 - fearofawhackplanet
@fearofawhackplanet:是的,现在可以了。但在添加额外段落之前是不行的。 - Philippe Leybaert
1
这是一个非常完整和详尽的答案,尽管我发现有趣的是,对于带符号的32位整数,~运算符会像处理无符号8位字节一样处理数字。 - MiffTheFox

17
这种行为是所有.NET编译器生成的中间语言(IL)设计的结果。虽然它支持短整数类型(byte,sbyte,short,ushort),但对它们的操作非常有限。只有加载、存储、转换和创建数组等操作。这不是偶然的,当IL被设计并且RISC是未来时,这些是你可以在32位处理器上高效执行的操作。
二进制比较和分支操作仅适用于int32、int64、本机int、本机浮点、对象和托管引用。这些操作数在任何当前CPU核心上都是32位或64位,确保JIT编译器可以生成高效的机器代码。
你可以在Ecma 335,Partition I,chapter 12.1和Partition III,chapter 1.5中了解更多信息。
我写了一篇更详细的文章在这里

让我想知道这种隐式转换是否会带来处理成本...我最终进行位运算的数据类型通常是旧的图像格式(例如位平面组合/拆分),因此需要处理数万字节。 - Nyerguds
@Nyerguds 对本地类型的强制转换可能是在编译成IL时完成的,因此您在C#中定义的任何字节都会以uint形式出现在IL中。这意味着在除了编译时间之外的任何时候都不应该有性能损失,而编译时间的影响非常小。 - cvbattum

5

对于byte类型(以及其他类型),二元运算符未定义。事实上,所有二元(数值)运算符作用于以下本机类型:

  • int
  • uint
  • long
  • ulong
  • float
  • double
  • decimal

如果涉及任何其他类型,则会使用上述其中之一。

这都在C#规范版本5.0中了(第7.3.6.2节):

预定义的+、-、*、/、%、&、|、^、==、!=、>、<、>=和<=二元运算符的操作数会发生二进制数字提升。二进制数字提升隐式地将两个操作数转换为公共类型,对于非关系运算符,该公共类型还成为操作的结果类型。二进制数字提升包括按照它们出现的顺序应用以下规则:

  • 如果任一操作数为decimal类型,则将另一个操作数转换为decimal类型,或者如果另一个操作数为float或double类型,则会出现编译时错误。
  • 否则,如果任一操作数为double类型,则将另一个操作数转换为double类型。
  • 否则,如果任一操作数为float类型,则将另一个操作数转换为float类型。
  • 否则,如果任一操作数为ulong类型,则将另一个操作数转换为ulong类型,或者如果另一个操作数为sbyte、short、int或long类型,则会出现编译时错误。
  • 否则,如果任一操作数为long类型,则将另一个操作数转换为long类型。
  • 否则,如果任一操作数为uint类型,并且另一个操作数为sbyte、short或int类型,则将两个操作数都转换为long类型。
  • 否则,如果任一操作数为uint类型,则将另一个操作数转换为uint类型。
  • 否则,将两个操作数都转换为int类型。

1
基本上,这是因为C#设计者认为为位运算符(不会溢出)编写单独的规则与算术运算符(可能会溢出)的规则分开成本太高了。证明让编译器插入隐式转换是安全的微不足道,因为值始终在较窄类型的范围内。但显然,指定、编写和测试该行为的成本被认为大于收益。 - Ben Voigt
1
实际上,这与位运算符有无并没有关系。只是没有作用于字节类型的运算符。 - Philippe Leybaert
无论CLR是否定义了这些操作符,都不会阻止C#表现得好像它有这样的操作符,就像C#将每个未重载“==”运算符的类类型都视为具有检查该类型两个对象的重载一样。 - supercat

2

这是因为 & 是定义在整数上的,而不是字节上的,编译器会隐式地将两个参数转换为整数。


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