对ref T的TypedReference抛出BadImageFormatException

时间:2014-11-14 10:23:56

标签: c# il typedreference

我有以下示例代码:

string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
Int32.TryParse(a, out __refvalue(tr, int));

应该将1放入b。问题是,它会抛出一个BadImageFormatException: Bad class token。问题当然是out __refvalue(tr, int)表达式。 __refvalue被编译为refanyval操作码,该操作码应返回存储在类型引用中的地址。然后,out(同样适用于ref)关键字应该将其转换为ref int并将(未更改的)引用传递给TryParse方法。

问题出现在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

现在问题显而易见了 - refanyval 0。操作码应该采用类型参数,因此0完全不合适,应该是refanyval [mscorlib]System.Int32

这是编译器中的错误吗?有没有办法绕过这个bug?感谢您的意见。

编辑:所以我构建了一个很好的方法来生成IL,允许在TypedReferenceref 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));
        }
    }
}

用法

string a = "1";
int b = 0;
TypedReference tr = __makeref(b);
ReferenceHelper.PassReference(tr, delegate(out int val){return Int32.TryParse(a, out val);});

1 个答案:

答案 0 :(得分:4)

问题是out - 编译器不知道如何正确地编译out __refvalue(或者ref __refvalue,这就不足为奇了,因为功能__refvalue支持(varargs)不需要与out结合使用。这是一个错误,因为编译器不应该发出无效代码;这不是一个错误,如果您使用未记录的关键字,您自己(并且MS对修复因使用它们而导致的错误不感兴趣,如果Connect票证是任何指示)。< / p>

任何解决方法都将涉及不使用out参数,但这是否可行取决于您的方案(您可以编写一个围绕Int32.TryParse的包装器来返回一个元组,或者将参数括起来,例如,但这当然是一个简化的实例)。更好的解决方法是不使用未记录的关键字 - 您可能需要咬紧牙关并进行动态IL生成(然后我无法想象您需要mkrefany / refanyval)。< / p>