5

我正在调查此 C# 代码的执行情况:

public static void Test<T>(object o) where T : class
{
    T t = o as T;
}

等效的 IL 代码是:

.method public static void  Test<class T>(object A_0) cil managed
{
  // Code size       13 (0xd)
  .maxstack  1
  .locals init (!!T V_0)
  IL_0000:  ldarg.0
  IL_0001:  isinst     !!T
  IL_0006:  unbox.any  !!T
  IL_000b:  stloc.0
  IL_000c:  ret
} // end of method DemoType::Test

基于这个答案(不必要的 unbox_any),任何人都可以向我解释 Jitter 在这里做什么的确切逻辑吗?在这种特定情况下,Jitter 究竟是如何决定忽略“unbox_any”指令的(理论上,根据msdn,当 isinst 指令产生 null 时应该抛出 NullReferenceException ,但这在实践中不会发生!)

更新

根据 usr 的回答和 Hans 的评论,如果obj是引用类型,castclass将被调用,因此,没有 NRE。

但是下面的案例呢?

static void Test<T>(object o) where T : new()
    {
        var nullable = o as int?;
        if (nullable != null)
            //do something
    }

Test<int?>(null);

以及等效的 IL 代码(部分):

IL_0001:  ldarg.0
IL_0002:  isinst     valuetype [mscorlib]System.Nullable`1<int32>
IL_0007:  unbox.any  valuetype [mscorlib]System.Nullable`1<int32>
IL_000c:  stloc.0
IL_000d:  ldloca.s   nullable
IL_000f:  call       instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
IL_0014:  stloc.1
IL_0015:  ldloc.1
IL_0016:  brfalse.s  IL_0024

在这种情况下,它的值类型为什么不抛出 NRE?

4

2 回答 2

1

当应用于引用类型时,unbox.any 指令与 castclass typeTok 具有相同的效果。

T被限制为引用类型。在这种情况下,该指令不会引发 NRE。JIT 不会“忽略”它,它会按规定执行它。JIT 不允许忽略指令。

文档有声明

如果 obj 是空引用,则会引发 NullReferenceException。

这是误导性的,因为它仅适用于值类型。我引用的第一句话是明确的。

于 2015-12-20T16:33:08.573 回答
1

要回答可空值类型的第二种情况(问题的更新部分),我们需要仔细查看 ECMA CLI 规范(III.4.33 unbox.any – 将装箱类型转换为值):

如果 obj 为 null并且 typeTok 是不可为空的值类型,则抛出 System.NullReferenceException

MSDN 文档中缺少粗体部分。

所以总结一下unbox_any的行为:

  1. 如果 typeTok 是 ref 类型,则与 castclass 的行为相同
  2. 如果 typeTok 是值类型:

    2.1。如果 obj 为 null 且 typeTok 为可为 null 的值类型,则结果为 null

    2.2. 如果 obj 为 null 且 typeTok 不是可空值类型,则会抛出NullReferenceException

如果我理解正确,第 2.2 段的行为与常规拆箱操作相同,请参阅 jitter source code

于 2015-12-21T06:46:51.907 回答