Domanda

In Microsoft IL, per chiamare un metodo su un tipo di valore è necessario un riferimento indiretto.Diciamo che abbiamo un ILGenerator chiamato "il" e che attualmente abbiamo un Nullable in cima allo stack, se vogliamo verificare se ha un valore allora potremmo emettere quanto segue:

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);

Tuttavia sarebbe carino evitare di salvarlo come variabile locale e chiamare semplicemente il metodo sull'indirizzo della variabile già nello stack, qualcosa del tipo:

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

La famiglia di istruzioni ldind sembra promettente (in particolare ldind_ref) ma non riesco a trovare documentazione sufficiente per sapere se ciò causerebbe l'inscatolamento del valore, cosa che sospetto possa accadere.

Ho dato un'occhiata all'output del compilatore C#, ma utilizza variabili locali per raggiungere questo obiettivo, il che mi fa credere che il primo modo potrebbe essere l'unico modo.Qualcuno ha qualche idea migliore?

**** Modificare:Note aggiuntive ****

Il tentativo di chiamare direttamente il metodo, come nel programma seguente con le righe commentate, non funziona (l'errore sarà "L'operazione potrebbe destabilizzare il runtime").Togli il commento dalle righe e vedrai che funziona come previsto, restituendo "True".

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));

Quindi non puoi semplicemente chiamare il metodo con il valore nello stack perché è un tipo di valore (anche se potresti farlo se fosse un tipo di riferimento).

Quello che mi piacerebbe ottenere (o sapere se è possibile) è sostituire le tre righe che vengono mostrate commentate, ma mantenere il programma funzionante, senza utilizzare un local temporaneo.

È stato utile?

Soluzione

Se la variabile è già nello stack, puoi andare avanti ed emettere semplicemente la chiamata al metodo.

Sembra che il costruttore non inserisca la variabile nello stack in una forma digitata.Dopo aver approfondito un po' l'IL, sembra che ci siano due modi per utilizzare la variabile dopo averla costruita.

Puoi caricare la variabile che memorizzerà il riferimento nello stack di valutazione prima di chiamare il costruttore, quindi caricare nuovamente quella variabile dopo aver chiamato il costruttore in questo modo:

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));

L'altra opzione è farlo nel modo che hai mostrato.L'unica ragione che posso vedere è che i metodi ctor restituiscono void, quindi non inseriscono il loro valore nello stack come altri metodi.Sembra strano che tu possa chiamare Setloc se il nuovo oggetto non è nello stack.

Altri suggerimenti

Dopo aver esaminato le opzioni con ulteriori e ulteriori considerazioni, penso che tu abbia ragione nel ritenere che non sia possibile farlo.Se esamini il comportamento dello stack delle istruzioni MSIL, puoi vedere che nessuna operazione lascia i suoi operandi nello stack.Poiché questo sarebbe un requisito per un'operazione "ottieni indirizzo di voce stack", sono abbastanza sicuro che non ne esista.

Questo ti lascia con dup+box o stloc+ldloca.Come hai sottolineato, quest'ultimo è probabilmente più efficiente.

@greg:Molte istruzioni lasciano il loro risultato in pila, ma nessuna istruzione ne lascia nessuna operandi sullo stack, che sarebbe richiesto per un'istruzione "ottieni indirizzo elemento stack".

L'avevo capito!Per fortuna stavo leggendo del unbox opcode e ho notato che spinge il file indirizzo del valore. unbox.any spinge il valore effettivo.Quindi, per chiamare un metodo su un tipo di valore senza doverlo memorizzare in una variabile locale e quindi caricare il suo indirizzo, puoi semplicemente box seguito da unbox.Usando il tuo ultimo esempio:

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));

Lo svantaggio è che il boxing provoca l'allocazione della memoria per l'oggetto boxed, quindi è un po' più lento rispetto all'utilizzo delle variabili locali (che verrebbero già allocate).Ma ti evita di dover determinare, dichiarare e fare riferimento a tutte le variabili locali di cui hai bisogno.

Ho appena scritto una classe che fa ciò che chiede l'OP ...ecco il codice IL prodotto dal compilatore 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top