-
-
Save AArnott/d285feef75c18f6ecd2b to your computer and use it in GitHub Desktop.
namespace ILExaminer | |
{ | |
using System; | |
static class Program | |
{ | |
internal static Func<T> AsFunc<T>(this T value) | |
where T : class | |
{ | |
return new Func<T>(value.Return); | |
} | |
private static T Return<T>(this T value) | |
{ | |
return value; | |
} | |
static void Main(string[] args) | |
{ | |
Func<string> foo = "hi".AsFunc(); | |
string v = foo(); | |
} | |
} | |
} |
.class private abstract auto ansi sealed beforefieldinit ILExaminer.Program | |
extends [mscorlib]System.Object | |
{ | |
.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) | |
.method assembly hidebysig static class [mscorlib]System.Func`1<!!T> | |
AsFunc<class T>(!!T 'value') cil managed | |
{ | |
.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) | |
// Code size 18 (0x12) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: box !!T | |
IL_0006: ldftn !!0 ILExaminer.Program::Return<!!0>(!!0) | |
IL_000c: newobj instance void class [mscorlib]System.Func`1<!!T>::.ctor(object, | |
native int) | |
IL_0011: ret | |
} // end of method Program::AsFunc | |
.method private hidebysig static !!T Return<T>(!!T 'value') cil managed | |
{ | |
.custom instance void [mscorlib]System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) | |
// Code size 2 (0x2) | |
.maxstack 8 | |
IL_0000: ldarg.0 | |
IL_0001: ret | |
} // end of method Program::Return | |
.method private hidebysig static void Main(string[] args) cil managed | |
{ | |
.entrypoint | |
// Code size 19 (0x13) | |
.maxstack 1 | |
.locals init ([0] class [mscorlib]System.Func`1<string> foo) | |
IL_0000: ldstr "hi" | |
IL_0005: call class [mscorlib]System.Func`1<!!0> ILExaminer.Program::AsFunc<string>(!!0) | |
IL_000a: stloc.0 | |
IL_000b: ldloc.0 | |
IL_000c: callvirt instance !0 class [mscorlib]System.Func`1<string>::Invoke() | |
IL_0011: pop | |
IL_0012: ret | |
} // end of method Program::Main | |
} // end of class ILExaminer.Program | |
As discussed on Twitter this is will be a no-op and it's not a bug.
Relevant excerpt from CLI spec III.4.1:
If typeTok is a reference type, the box instruction does returns val unchanged as obj.
If typeTok is a generic parameter, the behavior of box instruction depends on the actual type at
runtime. If this type is a value type it is boxed as above, if it is a reference type then val is not
changed.
I suspect there's no point in special casing the class constraint in the compiler when it would merely save a no-op in IL.
OK, I figured out why this box must be emitted even if T is constrained to be a reference type. It is required to be verifiable. The key is in the next sentence in the spec that I left out of the above excerpt:
However the type tracked by verification is always “boxed” typeTok for generic
parameters, regardless of whether the actual type at runtime is a value or reference type.
If you strip out that box via ildasm/ilasm and run peverify, you get something like:
[IL]: Error: [C:\Program.exe : Program::Foo[T]]
[offset 0x00000002]
[found (unboxed) 'T']
[expected ref 'System.Object']
Unexpected type on the stack.
So from the verifier's perspective, you simply can't pass an 'unboxed' T to object. It does not take in to account the constraint and that makes sense when you factor in that requiring the box has no impact on the native code that will run.
Since the whole point of this is to avoid the allocation of a closure, the fact that C# is emitting a
box
IL instruction in theAsFunc<T>
extension method makes me think I'm not actually winning anything by this.Since C# (for some reason) requires that I add
where T : class
to the AsFunc method, it seems pointless to box the T value.Will the JIT skip over this instruction? Is this a bug in the C# compiler?