Pregunta

En Microsoft IL, para llamar a un método en un tipo de valor necesita una referencia indirecta.Digamos que tenemos un ILGenerator llamado "il" y que actualmente tenemos un valor Nullable en la parte superior de la pila, si queremos verificar si tiene un valor, entonces podríamos emitir lo siguiente:

var local = il.DeclareLocal(typeof(Nullable<int>));
il.Emit(OpCodes.Stloc, local);
il.Emit(OpCodes.Ldloca, local);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

Sin embargo, sería bueno omitir guardarlo como una variable local y simplemente llamar al método en la dirección de la variable que ya está en la pila, algo como:

il.Emit(/* not sure */);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

La familia de instrucciones ldind parece prometedora (particularmente ldind_ref), pero no puedo encontrar suficiente documentación para saber si esto provocaría un bloqueo del valor, lo cual sospecho que podría ser así.

He echado un vistazo a la salida del compilador de C#, pero utiliza variables locales para lograr esto, lo que me hace creer que la primera forma puede ser la única.¿Alguien tiene alguna idea mejor?

**** Editar:Notas adicionales ****

Intentar llamar al método directamente, como en el siguiente programa con las líneas comentadas, no funciona (el error será "La operación podría desestabilizar el tiempo de ejecución").Descomente las líneas y verá que funciona como se esperaba y devuelve "Verdadero".

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
//var local = il.DeclareLocal(typeof(Nullable<int>));
//il.Emit(OpCodes.Stloc, local);
//il.Emit(OpCodes.Ldloca, local);
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

Por lo tanto, no puede simplemente llamar al método con el valor en la pila porque es un tipo de valor (aunque podría hacerlo si fuera un tipo de referencia).

Lo que me gustaría lograr (o saber si es posible) es reemplazar las tres líneas que se muestran comentadas, pero mantener el programa funcionando, sin usar un local temporal.

¿Fue útil?

Solución

Si la variable ya está en la pila, puede continuar y simplemente emitir la llamada al método.

Parece que el constructor no inserta la variable en la pila en forma escrita.Después de profundizar un poco en IL, parece que hay dos formas de usar la variable después de construirla.

Puedes cargar la variable que almacenará la referencia en la pila de evaluación antes de llamar al constructor y luego cargar esa variable nuevamente después de llamar al constructor de esta manera:

DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
ILGenerator il = method.GetILGenerator();
Type nullable = typeof(Nullable<int>);
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) });
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod();
LocalBuilder value = il.DeclareLocal(nullable);         

// load the variable to assign the value from the ctor to
il.Emit(OpCodes.Ldloca_S, value);
// load constructor args
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Call, ctor);
il.Emit(OpCodes.Ldloca_S, value);

il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(method.Invoke(null, null));

La otra opción es hacerlo como te has mostrado.La única razón que puedo ver para esto es que los métodos ctor devuelven void, por lo que no ponen su valor en la pila como otros métodos.Parece extraño que puedas llamar a Setloc si el nuevo objeto no está en la pila.

Otros consejos

Después de analizar las opciones un poco más y considerarlas más a fondo, creo que tiene razón al suponer que no se puede hacer.Si examina el comportamiento de la pila de instrucciones MSIL, puede ver que ninguna operación deja sus operandos en la pila.Dado que este sería un requisito para una operación de "obtener dirección de entrada de pila", estoy bastante seguro de que no existe.

Eso te deja con dup+box o stloc+ldloca.Como ha señalado, es probable que esto último sea más eficiente.

@greg:Muchas instrucciones dejan su resultado en la pila, pero ninguna instrucción deja ninguno de sus operandos en la pila, que sería necesario para una instrucción 'obtener dirección del elemento de la pila'.

¡Me lo imaginé!Por suerte estaba leyendo sobre el unbox código de operación y noté que empuja el DIRECCIÓN del valor. unbox.any empuja el valor real.Entonces, para llamar a un método en un tipo de valor sin tener que almacenarlo en una variable local y luego cargar su dirección, simplemente puede box seguido por unbox.Usando su último ejemplo:

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox
il.Emit(OpCodes.Unbox, typeof(Nullable<int>));
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

La desventaja de esto es que el boxeo provoca la asignación de memoria para el objeto encajonado, por lo que es un poco más lento que usar variables locales (que ya estarían asignadas).Pero le evita tener que determinar, declarar y hacer referencia a todas las variables locales que necesita.

Acabo de escribir una clase que hace lo que pide el OP...Aquí está el código IL que produce el compilador de C#:

  IL_0008:  ldarg.0
  IL_0009:  ldarg.1
  IL_000a:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
  IL_000f:  stfld      valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X
  IL_0014:  nop
  IL_0015:  ret
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top