我不明白为什么下面的代码可以编译。
public void Overflow()
{
Int16 s = 32767;
s = (Int16) (s + 1);
}
在编译时,很明显 (s+1) 不再是 Int16,因为我们知道 s 的值。
并且 CLR 允许转换为:
因为 Int32 不是 Int16,并且 Int16 也不是 Int32 的基类型。
问题:那么为什么编译器不会因上述转换而失败呢?您能从 CLR 和编译器的角度解释一下吗?
表达式
s + 1
的类型为 Int32
- 在执行加法之前,两个操作数都转换为 Int32
。所以你的代码相当于:
public void Overflow()
{
Int16 s = 32767;
s = (Int16) ((Int32) s + (Int32) 1);
}
因此溢出实际上只发生在显式强制转换中。
或者,换句话说:因为语言规范是这么说的。您应该描述以下之一:
编辑:只是为了让事情真正清楚(根据您的评论),编译器不允许这样做:
s = s + 1;
当
s
是 Int16
whatever 时,s
的值可能是已知的。没有 Int16 operator+ (Int16, Int16)
运算符 - 如 C# 4 规范第 7.8.4 节所示,整数加法运算符是:
int operator +(int x, int y);
uint operator +(uint x, uint y);
long operator +(long x, long y);
ulong operator +(ulong x, ulong y);
一般来说,强制转换表示“我是故意这样做的,不要抱怨”,因此编译器抱怨将是令人惊讶的行为。
事实上,由于参数的隐式提升,没有溢出。 然而,转换会截断 32 位结果,因此结果在算术上不等于
s + 1
;但因为您明确请求强制转换,编译器不会抱怨 - 它完全按照您的要求进行。
此外,在很多情况下,“环绕”溢出(或模 2n 算术)是故意且需要的。如果您显式转换为较小的类型,编译器将合理地假设它是必需的。
程序员需要为操作选择合适的类型,如果不希望出现溢出,则
float
、double
或 decimal
可能是比系统限制的整数类型更合适的算术类型。
“在编译时,很明显 (s+1) 不再是 Int16,因为我们知道 s 的值。”
我们知道 s + 1 的值对于短期来说太大了;编译器没有。编译器知道三件事:
是的,在这种特定情况下,当您进行类型转换时,确定结果太大而无法放入short中是很简单的,但是要确定编译器需要在编译时执行算术,然后执行类型转换对结果进行验证检查。除了非常罕见的例外情况(所有都在规范中明确指出,主要涉及零值常量),编译器不会检查操作的results,只检查操作的types,并且操作的类型都是正确的。
此外,编译器允许强制转换的情况列表还远远不够。编译器允许在多种情况下进行类型转换,其中许多对于 CLR 来说是完全不可见的。例如,语言中内置了隐式和显式类型转换,几乎所有数字类型都可以转换为其他所有数字类型。查找有关类型转换规则的其他信息的好地方:
感谢大家的解释。
我还要添加 Jeffry Richter 书中的引文,它解释了为什么当您尝试将 Int16 转换为 Int32 时编译器不会失败,而它们不是彼此派生的:
来自第116页:
” (...) C# 编译器 对原始类型有深入的了解,并在编译时应用其自己的特殊规则 代码。换句话说,编译器可以识别常见的编程模式并 生成必要的 IL 以使编写的代码按预期工作。”