Question

Dans Microsoft IL, pour appeler une méthode sur un type de valeur, vous avez besoin d'une référence indirecte. Disons que nous avons un générateur IL nommé "il". et qu'actuellement nous avons un Nullable en haut de la pile, si nous voulons vérifier s'il a une valeur, nous pourrions émettre ce qui suit:

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

Cependant, il serait bon de ne pas l'enregistrer en tant que variable locale et d'appeler simplement la méthode à l'adresse de la variable déjà sur la pile, quelque chose comme:

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

La famille d'instructions ldind semble prometteuse (en particulier ldind_ref), mais je ne trouve pas suffisamment de documentation pour savoir si cela entraînerait un encaissement de la valeur, ce qui, je suppose, pourrait en être ainsi.

J'ai jeté un coup d'œil à la sortie du compilateur C #, mais il utilise des variables locales pour y parvenir, ce qui me laisse penser que le premier moyen peut être le seul moyen. Quelqu'un a de meilleures idées?

**** Edit: Notes complémentaires ****

Tenter d'appeler directement la méthode, comme dans le programme suivant avec les lignes commentées, ne fonctionne pas (l'erreur sera "L'opération pourrait déstabiliser l'exécution"). Décommentez les lignes et vous verrez que cela fonctionne comme prévu, renvoyant "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));

Vous ne pouvez donc pas simplement appeler la méthode avec la valeur sur la pile, car il s'agit d'un type de valeur (bien que vous puissiez le faire s'il s'agissait d'un type de référence).

Ce que j'aimerais réaliser (ou savoir si cela est possible) est de remplacer les trois lignes affichées commentées, tout en maintenant le programme en marche, sans utiliser de local temporaire.

Était-ce utile?

La solution

Si la variable est déjà sur la pile, vous pouvez continuer et simplement émettre l'appel de méthode.

Il semble que le constructeur ne pousse pas la variable sur la pile sous une forme typée. Après avoir creusé un peu l’IL, il apparaît qu’il ya deux façons d’utiliser la variable après sa construction.

Vous pouvez charger la variable qui stockera la référence sur la pile d'évaluation avant d'appeler le constructeur, puis recharger cette variable après avoir appelé le constructeur comme suit:

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’autre option est de procéder comme vous l’avez montré. La seule raison pour cela que je peux voir est que les méthodes ctor retournent void, elles ne mettent donc pas leur valeur sur la pile comme les autres méthodes. Il semble étrange que vous puissiez appeler Setloc si le nouvel objet ne se trouve pas sur la pile.

Autres conseils

Après avoir examiné certaines options de plus en plus approfondies, je pense que vous avez raison de supposer que cela ne peut pas être fait. Si vous examinez le comportement de la pile dans les instructions MSIL, vous pouvez vous rendre compte qu'aucun opérateur ne laisse ses opérandes sur la pile. Etant donné que ceci serait une exigence pour un op "Obtenir l'adresse de la pile", je suis assez confiant qu'il n'en existe pas.

Cela vous laisse avec dup + box ou stloc + ldloca. Comme vous l'avez souligné, ce dernier est probablement plus efficace.

@greg: de nombreuses instructions laissent leur résultat sur la pile, mais aucune instruction ne laisse aucun de leurs opérandes sur la pile, ce qui serait requis pour une pile 'get élément adresse 'instruction.

Je l'ai compris! Heureusement, à propos de l'opcode unbox , je me suis rendu compte qu'il contenait l'adresse de la valeur. unbox.any renvoie la valeur réelle. Ainsi, pour appeler une méthode sur un type de valeur sans avoir à la stocker dans une variable locale, puis à charger son adresse, vous pouvez simplement box suivi de unbox . En utilisant votre dernier exemple:

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

L’inconvénient, c’est que la boxe entraîne l’allocation de mémoire pour l’objet boxé, c’est donc un peu plus lent que l’utilisation de variables locales (qui seraient déjà allouées). Mais cela vous évite de devoir déterminer, déclarer et référencer toutes les variables locales dont vous avez besoin.

Vient d'écrire une classe qui fait ce que l'OP demande. Voici le code IL produit par le compilateur 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top