混合包装类型和常量整型表达式的条件运算符

本文关键字:整型 表达式 条件运算符 常量 包装 包装类 类型 混合 | 更新日期: 2023-09-27 18:07:51

当我将条件运算符的第二个和第三个操作数(较窄的可空值和较宽的数值)组合为条件运算符时,我在c#中得到了我不期望的东西。这不起作用,但我发现如果更宽的数值是int类型的常量表达式,而更窄的可空值是SByte类型?还是Int16 ?。演示:

bool test = true;
Int16? aShort = 5;
Int32 anInt = 5;
const Int32 aConstInt = 4;
Object o1 = test ? aShort : anInt;      // does not compile
Object o2 = test ? aShort : aConstInt;  // does compile

我的问题是为什么它编译如果我的int是一个常量?我在c#语言规范中找不到这方面的参考,这里发生了什么?

混合包装类型和常量整型表达式的条件运算符

在c# 4.0语言规范中,§7.14(条件运算符)声明如下:

?:运算符的第二个和第三个操作数xy控制条件表达式的类型。

如果x的类型为X, y的类型为Y,则

  • 如果存在从XY的隐式转换,但不存在从YX的隐式转换,则Y是条件表达式的类型。
  • 如果存在从YX的隐式转换(§6.1),但没有从XY的隐式转换,则X是条件表达式的类型。
  • 否则,无法确定表达式类型,并发生编译时错误。

和§6.1.9(隐式常量表达式转换)声明如下:

隐式常量表达式转换允许以下转换:

  • 类型为int常量表达式(§7.19)可以转换为类型为sbytebyteshortushortuintulong,前提是常量表达式的值在目的类型的范围内。
  • 类型为long常量表达式可以转换为类型为ulong常量表达式的值不为负。

可以看到,intlong类型的常量表达式被特殊处理。

因此表达式test ? aShort : aConstInt是有效的,因为存在从int常量表达式4short再到short?的隐式转换(因此表达式的类型是short?),但test ? aShort : anInt无效,因为既没有从int类型的非常量表达式到short?的隐式转换,也没有从short?int的隐式转换。

当你写:

const Int32 aConstInt = 4;
Object o2 = test ? aShort : aConstInt;  // does compile

编译器能够处理第二行中的aConstInt引用,就好像您在那里简单地放置了一个4。根据上下文,它将4转换为short,而不是Int32。如果编译器只知道输入是Int32,那么它的行为就会不同,因为它不是const

如果你已经声明:

const Int32 aConstInt = short.MaxValue + 1;

那么编译器将不允许编译同一行:

Object o2 = test ? aShort : aConstInt;  // does not compile

因为它现在将其视为32768,而不是short

c#编译器对常量有特殊的处理。这个特殊的属性甚至可以跨表达式传播,比如1+2null和lambda也很特别:它们没有CLR类型,但你可以将它们转换为任何引用类型或任何匹配的委托类型。(null不是对象类型的).

这是c#规范强制要求的,它强制实现解析常量表达式并维护它们的可转换属性。

的例子:

short x = 1; //assigning an integer literal to a short - works because it is a constant
short y = 1+2; //also works for expressions

也可以将long型赋值给int型而不进行强制类型转换:

const Int64 a = 1;
Int32 b = a;

常量只是传播。

你的第一个条件不编译的原因不是因为const;问题在于数据类型。aShort是一个Nullable<Int16>,如果你将它声明为一个正常的Int16,它会工作得很好

Int16 aShort = 5; // not nullable
Int32 anInt = 5;
Object o1 = test ? aShort : anInt;      // does compile

当你有? (short?) : (int)时,它无法进行转换,因为编译器不知道如何将(short?)转换为int

当您有? (short) : (int)时,运行时short转换为int以适应更大的数据类型。

然而,第二个条件可以编译,这要归功于c# 编译器在处理const整数时使用的一些特殊魔法。当您有? (short?) : (const int = 4)时,编译器将const int视为short,因为您已将其声明为适合short数据类型的值。然后在结果中将short隐式地转换为short?

如果你有? (short) : (const int = 4),它的行为就像第一种情况,在运行时将short上转换为int

如果你有? (short?) : (short?)工作,因为编译器知道如何隐式地将short转换为short?

如果你在运行时查看结果类型,你实际上可以看到这个

Int16 aShort1 = 5; // not nullable
Int16? aShort2 = 5; // nullable
object o1 = test ? aShort1 : anInt;
object o2 = test ? aShort2 : aConstInt;
object o3 = test ? aShort1 : aConstInt;
object o4 = test ? aShort1 : aShort2;
o1.GetType() // System.Int32
o2.GetType() // System.Int16
o3.GetType() // System.Int32
o4.GetType() // System.Int16

实际上,如果您查看o2生成的IL,您将看到:

IL_001E:  ldarg.0     
IL_001F:  ldfld       UserQuery.test
IL_0024:  brtrue.s    IL_002E
IL_0026:  ldc.i4.4    // your integer constant
IL_0027:  newobj      System.Nullable<System.Int16>..ctor // Notice the type here
IL_002C:  br.s        IL_0034
IL_002E:  ldarg.0     
IL_002F:  ldfld       UserQuery.aShort2
IL_0034:  nop         
IL_0035:  box         System.Nullable<System.Int16>
IL_003A:  stloc.1     // o2