默认表达式

本文关键字:表达式 默认 | 更新日期: 2023-09-27 18:28:12

在C#规范中说,首先是:

如果默认值表达式中的类型在运行时计算为引用类型,结果为null并转换为该类型。如果类型在默认值表达式中,在运行时评估为值类型,结果是值类型的默认值(§4.1.2)。

因此,默认表达式似乎是在运行时计算的。。。但是之后说:

如果type是引用类型或已知为引用类型(§10.1.5)。此外,默认值表达式是常量表达式,如果该类型是以下值类型之一:sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double、decimal、bool或任何枚举类型。

那么,如果只检查一个运行时类型,default怎么能是一个常量表达式,从而在编译时求值呢?

例如,如果我写这样的东西:

J k = default(J);

其中J是类型参数,只有在运行时,当我们提供参数类型时,我们才能知道J是引用类型还是值类型。那么编译时会发生什么呢?

默认表达式

您只是看错了规范(重点是我的):

  • 默认值表达式是常量表达式(§7.19)如果类型是引用类型或已知为参考类型

这意味着,如果(并且仅当)类型或类型参数已知为引用类型(意味着它具有where T : class约束或使用default(SomeClass)),则表达式为常数。继续:

  • 此外,默认值表达式是常量表达式如果类型是以下值类型之一:sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal,bool或任何枚举类型

也就是说,出于某种原因,您正在使用default(sbyte)default(short)

例如,给出以下代码:

void Main()
{
    var x = default(byte);
    var y = default(M);
}
public struct M { }

发射的IL将是:

IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  stloc.0     // x
IL_0003:  ldloca.s    01 // y
IL_0005:  initobj     UserQuery.M
IL_000B:  ret  

对于byte,编译器可以发出0,它必须为我们的M结构调用initobj

它是在编译时评估的,因为J也是,你不在运行时提供参数类型,而是在编译时提供,所以如果你有一个类foo<J>,它包含一个方法栏,其中包含你在进行时提到的代码

foo<int> myfoo =new foo<int>(); // this is also evaluated at compile time
myfoo.bar(); // the compiler invokes the bar method of the foo<int> type and thus returns default(int)

如果默认值表达式中的类型在运行时评估为引用类型

如果类型在编译时计算为肯定引用类型或肯定不引用类型,则可以在编译时确定它在运行时计算为什么。

在具有类型参数T的泛型方法(或泛型类的方法)中,default(string)default(int)是这样,但default(T)不是这样(除非以某种方式受到约束,意味着它只能是另一个方法中的一个)。

因此,引用类型或非引用类型的质量只能在运行时推导出来。

然而,严格地说,它实际上可以在jit时间推导出来,而且这确实已经完成了,包括由于没有编译这个逻辑而已知已经死亡的代码,所以在:

if (default(T) == null)
{
   // code for reference-type or Nullable<> types of T
}
else
{
  // code for non-nullable value types of T
}

只有使用的分支才会被编译成机器代码,在任何一种情况下都会跳过比较本身。