当我比较可为空的短值时,编译器首先将它们转换为整数以与 null 进行比较。例如,考虑这个简单的代码:
short? cTestA;
if (cTestA == null) { ... }
它被编译器转换为:
short? CS$0$0001 = cTestA;
int? CS$0$0002 = CS$0$0001.HasValue ? new int?(CS$0$0001.GetValueOrDefault()) : null;
if (!CS$0$0002.HasValue){ ... }
所有 .NET 版本(包括 .NET 4)都会发生这种情况。
我在这里缺少什么?只是为了 HasValue 检查而进行双重转换的原因是什么?
我期望编译器做的是使用 .HasValue 进行简单的检查,
if (cTestA.HasValue){}
。至少这是我发现这种转换后在代码中所做的。
为什么要为如此简单的测试添加所有这些额外的代码?
回复:您的最新更新:
这是可空算术优化器中的一个错误。
当您执行以下操作时,可空优化器将删除不必要的到
int?
的转换:
short? s = null;
int? x = s + 1;
未优化的代码生成器的作用相当于:
short? s = null;
int? x;
int? temp = s.HasValue ? new int?((int)s.Value) : new int?();
x = temp.HasValue ? new int?(x.Value + 1) : new int?();
优化后的代码生成器相当于:
short? s = null;
int? x;
x = s.HasValue ? new int?((int)s.Value + 1) : new int?();
但是,优化器存在一个错误;我们不会为了平等而删除不必要的转换。
感谢您引起我的注意;我们将为Roslyn修复它。实际上,我将在接下来的几周内为 Roslyn 编写可空优化器。
更新:我确实编写了该优化器,如果您对它的工作原理感兴趣,我写了一系列关于它的文章,从这里开始:
http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/
请参阅C# 4.0 语言规范的第 4.1.5 节。特别感兴趣的是:
C# 支持九种整数类型:sbyte、byte、short、ushort、int、uint、long、ulong 和 char。 [省略文字]
整型一元和二元运算符始终与 有符号 32 位精度、无符号 32 位精度、有符号 64 位 精度,或无符号 64 位精度:
[省略要点]
对于二进制 +、–、*、/、%、&、^、|、==、!=、>、<, >= 和 <= operators, the operands are converted to type T, where T is the first of int, uint, long, and ulong that can fully represent all possible values of both operands. The operation is then performed using the precision of type T, and the type of the result is T (or bool for the relational operators). It is not permitted for one operand to be of type long and the other to be of type ulong with the binary operators.
使用short的操作被提升为int,并且这些操作被提升为可空的对应操作。 (这导致第 7.3.6.2 和 7.3.7 节)
好吧,这是设计使然,但仍然不明白他们为什么这样做,他们优化了字符串添加太多,为什么只留下数字并为这个简单的比较添加更多代码
这就是语言的设计方式,并考虑了现代架构的优化。不是专门针对这种情况,但请考虑 Eric Lippert 所说的here
C# 中的算术永远不会在短时间内完成。算术可以用 int、uint、long 和 ulong 来完成,但算术永远不能用 Short 来完成。 Shorts 提升为 int 并且算术是在 int 中完成的,因为就像我之前说过的,绝大多数算术计算都适合 int。绝大多数不适合短裤。在针对整数优化的现代硬件上,短算术可能会更慢,并且短算术不会占用任何更少的空间;它将在芯片上以整数或长整数形式完成。
您的最新更新:
我期望编译器做的是使用 .HasValue if (cTestA.HasValue){} 进行简单的检查,至少这是我在发现这种转换后对代码所做的事情。所以这就是我真的不明白为什么不做那么简单的思考而是添加所有这些额外的代码。编译器总是尝试优化代码 - 为什么这里要避免简单的 .HasValue 检查。我肯定在这里遗漏了一些东西......
我将不得不听取编译器专家的意见,解释为什么他们选择进行转换而不是立即进行 HasValue 检查,只是说可能只是有一个操作顺序。语言规范表示二元运算符操作数被提升,这就是他们在提供的代码片段中所做的事情。语言规范随后表示,使用
x == null
进行检查(其中 x 是可为空值类型)可以转换为 !x.HasValue
,这也是他们所做的。在您提供的编译代码中,数字提升只是优先于可为空的行为。
至于编译器总是尝试优化代码,专家可以再次澄清,但事实并非如此。它可以进行一些优化,而其他优化则可能取决于抖动。编译器或抖动可能会或可能不会进行某些优化,具体取决于它是调试还是发布版本,以及是否附加了调试器。毫无疑问,他们可以做出一些优化,但他们只是选择不这样做,因为成本与收益并没有发挥作用。