2014-11-14 3 views
2

Я следующий пример кода:TypedReference по реф T бросает BadImageFormatException

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.

Это ошибка в компиляторе? Есть ли способ обойти эту ошибку? Спасибо за ваши взгляды.

Edit: Так я построил хороший метод, который генерирует IL, позволяющий преобразование между TypedReference и 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)); 
     } 
    } 
} 

Использование

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

Я могу подтвердить это поведение ('BadImageFormatException') с помощью нового компилятора Visual C# и .NET Runtime. // Ключевые слова '__makeref' и' __refvalue' не входят в официальную спецификацию языка C#, так что сложно ли это ошибка или нет. Компилятор C# не обязан поддерживать это или обрабатывать его каким-либо определенным образом. «Обычно» при вызове метода может использоваться только переменная (локальная переменная, поле или запись массива) с 'ref' или' out'. Общее выражение (включая вызов 'get' доступа для свойства/индексатора) не допускается. –

+0

@JeppeStigNielsen Правда, логично, что 'get' accessor не может работать, потому что он возвращает само значение, а не адрес. Ключевое слово 'ref' можно использовать только по адресу. Таким образом, также работает 'ref * ptr' (а также' __refvalue' должен, поскольку он возвращает адрес). – IllidanS4

+0

Согласен. Было бы неплохо, если бы компилятор C# * либо отказался от этого (ошибка времени компиляции), * или * заставил его работать. –

ответ

4

Проблема заключается out - компилятор не знает, как правильно скомпилировать out __refvalue (или ref __refvalue, для этого ma tter), что неудивительно, так как опорные элементы __refvalue (varargs) не должны сочетаться с out. Это ошибка в том смысле, что компилятор не должен испускать неверный код; это не ошибка в том смысле, что если вы используете недокументированные ключевые слова, вы сами по себе (и MS не заинтересована в исправлении ошибок, которые являются результатом их использования, если для билетов Connect есть какие-либо признаки).

Любое временное решение будет включать в себя не используя out параметр, но будет ли это возможно, зависит от сценария (вы можете написать обертку Int32.TryParse, которая возвращает кортеж вместо или коробки аргумент, например, но, конечно, это упрощенный пример). Еще лучше обходиться без использования недокументированных ключевых слов - вам, возможно, придется кусать пулю и делать динамическое генерация IL (и тогда я не могу представить, что вам нужно mkrefany/refanyval).