Skip to content

Instantly share code, notes, and snippets.

@AArnott
Created August 21, 2014 21:21
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AArnott/d285feef75c18f6ecd2b to your computer and use it in GitHub Desktop.
Save AArnott/d285feef75c18f6ecd2b to your computer and use it in GitHub Desktop.
Creating a static method that accepts a first argument supplied by the delegate.
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
@AArnott
Copy link
Author

AArnott commented Aug 21, 2014

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 the AsFunc<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?

@nguerrera
Copy link

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.

@nguerrera
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment