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

C# 混合包装类型和常量整数表达式的条件运算符,c#,conditional-operator,C#,Conditional Operator,当我将一个更窄的可空值和一个更宽的数值组合为条件运算符的第二个和第三个操作数时,我得到了在C#中不期望的东西。这不起作用,但我发现如果更宽的数值是int类型的常量表达式,而更窄的nullable是SByte类型的,它就起作用了?还是Int16?。演示: bool test = true; Int16? aShort = 5; Int32 anInt = 5; const Int32 aConstInt = 4; Object o1 = test ? aShort : anInt; //

当我将一个更窄的可空值和一个更宽的数值组合为条件运算符的第二个和第三个操作数时,我得到了在C#中不期望的东西。这不起作用,但我发现如果更宽的数值是int类型的常量表达式,而更窄的nullable是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语言规范中找不到这方面的参考,这里发生了什么?

第一个条件没有编译的原因不是因为
常量;问题在于数据类型
aShort
是一个可为空的
null
,如果您将它声明为正常的
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
IL\u 001E:ldarg.0
IL_001F:ldfld UserQuery.test
IL_0024:brtrue.s IL_002E
IL_0026:ldc.i4.4//您的整数常量
IL_0027:newobj System.Nullable..ctor//注意这里的类型
IL_002C:br.s IL_0034
IL_002E:ldarg.0
IL_002F:ldfld UserQuery.aShort2
IL_0034:没有
IL_0035:盒式系统。可为空
IL_003A:stloc.1//o2
C#编译器专门处理常量。此特殊属性甚至可以跨表达式传播,如
1+2
null
和lambda也很特殊:它们没有CLR类型,但您可以将它们转换为任何引用类型或任何匹配的委托类型。(
null
不是object类型!)

这是C#spec规定的。它强制实现解析常量表达式并维护其可转换属性

例如:

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 Int32 aConstInt = 4;
Object o2 = test ? aShort : aConstInt;  // does compile
编译器能够处理第二行中的
acontint
引用,就像您在那里简单地放置了
4
一样。根据上下文,它将
4
转换为
short
,而不是
Int32
。如果编译器只知道输入是
Int32
,则其行为会有所不同,而不是
const

如果您已声明:

const Int32 aConstInt = short.MaxValue + 1;
那么编译器将不允许编译同一行:

Object o2 = test ? aShort : aConstInt;  // does not compile
因为它现在将其视为
32768
,在C#4.0语言规范中它不是
短的

,§7.14(条件运算符)声明如下:

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

如果
x
具有类型
x
y
具有类型
y
,则

  • 如果存在从
    X
    Y
    的隐式转换(§6.1),而不是从
    Y
    X
    ,则
    Y
    是条件表达式的类型
  • 如果存在从
    Y
    X
    的隐式转换(§6.1),而不是从
    X
    Y
    ,则
    X
    是条件表达式的类型
  • 否则,无法确定表达式类型,并发生编译时错误
和§6.1.9(隐式常量表达式转换)规定如下:

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

  • int
    类型的常量表达式(§7.19)可以转换为类型
    sbyte
    byte
    short
    ushort
    uint
    ulong
    ,前提是常量表达式的值在目标类型的范围内
  • 如果常量表达式的值不是负值,则可以将
    long
    类型的常量表达式转换为
    ulong
    类型
如您所见,类型为
int
long
的常量表达式得到了特殊处理


因此表达式
test?aShort:acontint
有效,因为存在从
int
常量表达式
4
short
然后到
short?
的隐式转换(因此表达式的类型是
short?
),但是
测试?aShort:int
无效,因为既没有从类型为
int
的非常量表达式到
short?
的隐式转换,也没有从
short?
int
的隐式转换,但是为什么在仍然存在可为空的
时,它会使用
常量Int32
编译?请删除您的答案好吗?