Я следующий пример кода: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);});
Я могу подтвердить это поведение ('BadImageFormatException') с помощью нового компилятора Visual C# и .NET Runtime. // Ключевые слова '__makeref' и' __refvalue' не входят в официальную спецификацию языка C#, так что сложно ли это ошибка или нет. Компилятор C# не обязан поддерживать это или обрабатывать его каким-либо определенным образом. «Обычно» при вызове метода может использоваться только переменная (локальная переменная, поле или запись массива) с 'ref' или' out'. Общее выражение (включая вызов 'get' доступа для свойства/индексатора) не допускается. –
@JeppeStigNielsen Правда, логично, что 'get' accessor не может работать, потому что он возвращает само значение, а не адрес. Ключевое слово 'ref' можно использовать только по адресу. Таким образом, также работает 'ref * ptr' (а также' __refvalue' должен, поскольку он возвращает адрес). – IllidanS4
Согласен. Было бы неплохо, если бы компилятор C# * либо отказался от этого (ошибка времени компиляции), * или * заставил его работать. –