5

I'm investigating the execution of this C# code:

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

The equivalent IL code is:

.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

Based on this answer (unnecessary unbox_any), can anyone explain to me what the exact logic the Jitter is doing here; how exactly does the Jitter decide to ignore the 'unbox_any' instruction in this specific case (theoretically, according to msdn, a NullReferenceException should be thrown when the isinst instruction yields null, but this doesn't happen in practice!)

Update

Based on usr answer and Hans comment, if the obj is a reference type, castclass will be called, and therefore, no NRE.

But what about the following case?

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

Test<int?>(null);

And the equivalent IL code (partial):

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

In this case its value type so why NRE not thrown?

Community
  • 1
  • 1
Dudi Keleti
  • 2,946
  • 18
  • 33
  • `public static void Test(T o) where T : class` As never thrown runtime exception. – Hamlet Hakobyan Dec 20 '15 at 15:45
  • @HamletHakobyan you right in practice. but it should be according to msdn (see msdn link). the reason no exception has thrown is because the jitter emit unbox_any instruction (see other answer link). – Dudi Keleti Dec 20 '15 at 15:49
  • 1
    *"When applied to a reference type, the unbox.any instruction has the same effect as castclass typeTok. **If the operand typeTok is a generic type parameter, then the runtime behavior is determined by the type that is specified for that generic type parameter**."* Where in the docs does this specify a NRE? – Yuval Itzchakov Dec 20 '15 at 15:52
  • @YuvalItzchakov I saw that but i want to be sure what the exact logic. so, the jitter check for the type and if it is a reference type, unbox_any ignored? – Dudi Keleti Dec 20 '15 at 16:01
  • @YuvalItzchakov **"NullReferenceException is thrown if _obj_ is a null reference."** – Dudi Keleti Dec 20 '15 at 16:38
  • 2
    Just [look at the source](https://github.com/dotnet/coreclr/blob/master/src/jit/importer.cpp#L12612) if you want proof. Note the shortcut it takes on `!eeIsValueClass(resolvedToken.hClass)` – Hans Passant Dec 20 '15 at 17:07
  • @HansPassant Thanks! I did try to find it myself but without success. – Dudi Keleti Dec 20 '15 at 17:14

2 Answers2

1

When applied to a reference type, the unbox.any instruction has the same effect as castclass typeTok.

T is constrained to be a reference type. This instruction does not throw a NRE in this case. The JIT does not "ignore" it, it executes it as specified. The JIT is not allowed to ignore instructions.

The documentation has the statement

NullReferenceException is thrown if obj is a null reference.

which is misleading as it only applies to value types. The first statement that I quoted is unambiguous.

usr
  • 168,620
  • 35
  • 240
  • 369
  • in this specific case the isinst instruction return null and therefore the unbox_any should throw NRE. **"NullReferenceException is thrown if obj is a null reference."** so if the jitter doesn't "ignore" it, you should get NRE if you'll run this code. – Dudi Keleti Dec 20 '15 at 16:40
  • I have explained why that is not the case. If you disagree explain why. Don't just claim the answer is false. That prevents us from making progress. – usr Dec 20 '15 at 16:46
  • so if its a value type the obj must be a non null otherwise a NRE will be thrown, and if this is a reference type castclass logic will occur. it make sense but if its correct, why castclass logic need to happen if we just do something like this in the isinst and we already get null? i dont say that i disagree, i just ask. – Dudi Keleti Dec 20 '15 at 17:02
  • Talking about val types is moot here because T is constrained to be a ref type. If you remove the `where : class` constraint you can no longer use `as` (compiler error). – usr Dec 20 '15 at 17:17
  • I think I found the answer in source. I have yet to figure it out. thanks. – Dudi Keleti Dec 20 '15 at 18:38
  • The source is not the specification. It is not a good source for explanations. You already know what happens, reading the source does not add information.; The MSDN docs are not sufficient here. The ECMA CLI spec (http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf) says that value types are unboxed. It also says in `I.8.2.4 B` that `If the value type is a nullable type ... the result is a null reference or bitwise copy of its Value property`. Not perfectly specified I'd say but you can interpret these statements so that they mean what actually happens. – usr Dec 20 '15 at 18:43
  • Thanks! I'll check the code according to the spec tomorrow – Dudi Keleti Dec 20 '15 at 19:09
1

To answer on the second case (update section on the question) of nullable value type, we need to look closely in the ECMA CLI spec (III.4.33 unbox.any – convert boxed type to value):

System.NullReferenceException is thrown if obj is null and typeTok is a non-nullable value type

The bold part is missing from the MSDN documentation.

So to summarize the behavior of unbox_any:

  1. If typeTok is a ref type, same behavior as castclass
  2. If typeTok is a value type:

    2.1. If obj is null and typeTok is a nullable value type, the result is null

    2.2. If obj is null and typeTok is not a nullable value type, a NullReferenceException will thrown

If I understand correctly, the behavior of paragraph 2.2 is same as regular unbox operation see jitter source code

Dudi Keleti
  • 2,946
  • 18
  • 33