I have the following example code:
string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
Int32.TryParse(a, out __refvalue(tr, int));
It is supposed to put 1 into b
. The problem is, it throws a BadImageFormatException: Bad class token
. The problem is, of course, the out __refvalue(tr, int)
expression. __refvalue
gets compiled to the refanyval
opcode, which is supposed to return the address stored in the typed reference. The out
(the same applies to ref
) keyword is then supposed to cast it to ref int
and pass the (unchanged) reference to the TryParse
method.
The problem is found in the IL:
.locals init (string V_0, int32 V_1, typedref V_2)
IL_0000: ldstr "1"
IL_0005: stloc.0
IL_0006: ldc.i4.0
IL_0007: stloc.1
IL_0008: ldloca.s V_1
IL_000a: mkrefany [mscorlib]System.Int32
IL_000f: stloc.2
IL_0010: ldloc.0
IL_0011: ldloc.2
IL_0012: refanyval 0
IL_0017: call bool [mscorlib]System.Int32::TryParse(string, int32&)
IL_001c: pop
The problem is now obvious - refanyval 0
. The opcode is supposed to take a type argument, so 0
is completely out of place, it should be refanyval [mscorlib]System.Int32
.
Is this a bug in the compiler? Is there any way to bypass this bug? Thanks for your views.
Edit: So I have built a nice method that generates IL allowing conversion between TypedReference
and ref T
:
public static class ReferenceHelper
{
public delegate TResult OutDelegate<TArg,TResult>(out TArg variable);
public delegate TResult RefDelegate<TArg,TResult>(ref TArg variable);
public delegate void OutDelegate<TArg>(out TArg variable);
public delegate void RefDelegate<TArg>(ref TArg variable);
static readonly AssemblyBuilder ab;
static readonly ModuleBuilder mob;
static readonly Type TypedReferenceType = typeof(TypedReference);
static ReferenceHelper()
{
ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("ReferenceHelperAssembly"), AssemblyBuilderAccess.Run);
mob = ab.DefineDynamicModule("ReferenceHelperAssembly.dll");
}
public static TResult PassReference<TArg,TResult>(TypedReference tref, RefDelegate<TArg,TResult> del)
{
return ReferenceBuilder<TArg,TResult>.PassRef(tref, del);
}
public static void PassReference<TArg>(TypedReference tref, RefDelegate<TArg> del)
{
ReferenceBuilder<TArg>.PassRef(tref, del);
}
public static TResult PassReference<TArg,TResult>(TypedReference tref, OutDelegate<TArg,TResult> del)
{
return PassReference<TArg,TResult>(tref, delegate(ref TArg arg){return del(out arg);});
}
public static void PassReference<TArg>(TypedReference tref, OutDelegate<TArg> del)
{
PassReference<TArg>(tref, delegate(ref TArg arg){del(out arg);});
}
static int mcounter = 0;
private static Type BuildPassRef(Type deltype, Type argType, Type resultType)
{
TypeBuilder tb = mob.DefineType("ReferenceHelperType"+(mcounter++), TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Abstract);
MethodBuilder mb = tb.DefineMethod(
"PassRef",
MethodAttributes.Public | MethodAttributes.Static,
resultType,
new[]{TypedReferenceType, deltype}
);
var il = mb.GetILGenerator();
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Refanyval, argType);
il.Emit(OpCodes.Callvirt, deltype.GetMethod("Invoke"));
il.Emit(OpCodes.Ret);
return tb.CreateType();
}
private static class ReferenceBuilder<TArg,TResult>
{
public delegate TResult PassRefDelegate(TypedReference tref, RefDelegate<TArg,TResult> del);
public static readonly PassRefDelegate PassRef;
static ReferenceBuilder()
{
Type t = BuildPassRef(typeof(RefDelegate<TArg,TResult>), typeof(TArg), typeof(TResult));
PassRef = (PassRefDelegate)t.GetMethod("PassRef").CreateDelegate(typeof(PassRefDelegate));
}
}
private static class ReferenceBuilder<TArg>
{
public delegate void PassRefDelegate(TypedReference tref, RefDelegate<TArg> del);
public static readonly PassRefDelegate PassRef;
static ReferenceBuilder()
{
Type t = BuildPassRef(typeof(RefDelegate<TArg>), typeof(TArg), typeof(void));
PassRef = (PassRefDelegate)t.GetMethod("PassRef").CreateDelegate(typeof(PassRefDelegate));
}
}
}
Usage
string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
ReferenceHelper.PassReference(tr, delegate(out int val){return Int32.TryParse(a, out val);});
The problem is
out
-- the compiler doesn't know how to compileout __refvalue
properly (orref __refvalue
, for that matter), which is not surprising, since the feature__refvalue
supports (varargs) doesn't need to combine without
. This is a bug in the sense that the compiler isn't supposed to emit invalid code; this is not a bug in the sense that if you use undocumented keywords, you're on your own (and MS is not interested in fixing bugs that are the result of using them, if the Connect tickets are any indication).Any workaround will involve not using an
out
parameter, but whether this is possible depends on your scenario (you could write a wrapper aroundInt32.TryParse
that returns a tuple instead or boxes the argument, for example, but of course this is a simplified instance). An even better workaround is not to use undocumented keywords -- you may have to bite the bullet and do dynamic IL generation (and then I can't imagine you needmkrefany
/refanyval
).