2009-09-16 2 views
5

Dans .NET, en utilisant la réflexion, comment puis-je obtenir les variables de classe utilisées dans une méthode?Comment puis-je obtenir des champs utilisés dans une méthode (.NET)?

Ex:

class A 
{ 
    UltraClass B = new(..); 
    SupaClass C = new(..); 

    void M1() 
    { 
     B.xyz(); // it can be a method call 
     int a = C.a; // a variable access 
    } 
} 

Remarque: retourne GetClassVariablesInMethod (M1 MethodInfo) les variables B et C. Par variables, je veux dire la valeur et/ou le type et les paramètres du constructeur de cette variable spécifique.

+0

Je ne comprends pas ce que vous essayez de faire. Pourquoi avez-vous besoin de réflexion? Avec les "variables de classe", voulez-vous dire les champs? Vous pouvez facilement obtenir l'instance actuelle d'un certain champ, mais pas les arguments constructeur utilisés pour le créer. Pourquoi avez-vous besoin de cela? –

+0

Par variables de classe, j'entends les champs de portée de classe qui sont des classes. Je pense à déclarer un attribut pour certaines méthodes qui nécessite des choses spéciales à faire en fonction des variables qu'il utilise de sa classe parente. L'instance actuelle d'un certain champ peut fonctionner pour moi. – kerem

Répondre

9

Il y a beaucoup de réponses différentes, mais pas un seul appel à moi, voici le mien. Il utilise mon Reflection based IL reader.

est ici une méthode de récupération tous les champs utilisés par une méthode:

static IEnumerable<FieldInfo> GetUsedFields (MethodInfo method) 
{ 
    return (from instruction in method.GetInstructions() 
      where instruction.OpCode.OperandType == OperandType.InlineField 
      select (FieldInfo) instruction.Operand).Distinct(); 
} 
-1

La réflexion est principalement une API pour inspecter les métadonnées. Ce que vous essayez de faire, c'est inspecter l'IL brute qui n'est pas une fonction de réflexion supportée. La réflexion renvoie juste IL comme un octet brut [] qui doit être inspecté manuellement.

+1

pas très détaillé ... –

+1

@romkyns, ni votre commentaire. – JaredPar

+0

Indépendamment du commentaire de romkyns, votre réponse n'est en effet pas très détaillée. Deux autres réponses ici (la mienne et Jb Evain) ont une solution complète. – Timwi

0

Vous devez obtenir le MethodInfo. Appelez GetMethodBody() pour obtenir la structure du corps de la méthode, puis appelez GetILAsByteArray sur cela. La conversion de ce tableau octet en un flux de IL compréhensible.

grosso modo

public static List<Instruction> ReadIL(MethodInfo method) 
{ 
    MethodBody body = method.GetMethodBody(); 
    if (body == null) 
     return null; 

    var instructions = new List<Instruction>(); 
    int offset = 0; 
    byte[] il = body.GetILAsByteArray(); 
    while (offset < il.Length) 
    { 
     int startOffset = offset; 
     byte opCodeByte = il[offset]; 
     short opCodeValue = opCodeByte; 
     // If it's an extended opcode then grab the second byte. The 0xFE 
     // prefix codes aren't marked as prefix operators though. 
     if (OpCodeList[opCodeValue].OpCodeType == OpCodeType.Prefix 
      || opCodeValue == 0xFE) 
     { 
      opCodeValue = (short) ((opCodeValue << 8) + il[offset + 1]); 
      offset += 1; 
     } 
     // Move to the first byte of the argument. 
     offset += 1; 

     OpCode code = OpCodeList[opCodeValue]; 

     Int64? argument = null; 
     if (code.ArgumentSize() > 0) 
     { 
      Int64 arg = 0; 
      Debug.Assert(code.ArgumentSize() <= 8); 
      for (int i = 0; i < code.ArgumentSize(); ++i) 
      { 
       Int64 v = il[offset + i]; 
       arg += v << (i*8); 
      } 
      argument = arg; 
      offset += code.ArgumentSize(); 
     } 

     var instruction = new Instruction(startOffset, code, argument); 
     instructions.Add(instruction); 
    } 

    return instructions; 
} 

où OpCodeList est construit par

OpCodeList = new Dictionary<short, OpCode>(); 
foreach (var opCode in typeof (OpCodes).GetFields() 
         .Where(f => f.FieldType == typeof (OpCode)) 
         .Select(f => (OpCode) f.GetValue(null))) 
{ 
    OpCodeList.Add(opCode.Value, opCode); 
} 

Vous pouvez alors quelles instructions sont des appels de propriété IL ou membres apparence variables ups ou tout ce que vous avez besoin et résoudre ensuite par GetType() .Module.ResolveField. (Le code d'avertissement ci-dessus plus ou moins de travail mais a été arraché d'un plus grand projet que j'ai fait peut-être manquant des détails mineurs).

Edit: taille Argument est une méthode d'extension sur OpCode qui utilise juste une table à faire pour trouver la valeur appropriée

public static int ArgumentSize(this OpCode opCode) 
{ 
    Dictionary<OperandType, int> operandSizes 
      = new Dictionary<OperandType, int>() 
       { 
        {OperandType.InlineBrTarget, 4}, 
        {OperandType.InlineField, 4}, 
        {OperandType.InlineI, 4}, 
        // etc., etc. 
       }; 
    return operandSizes[opCode.OperandType]; 
} 

Vous trouverez tailles ECMA 335 que vous aurez également besoin Regardez les OpCodes pour trouver les OpCodes que vous recherchez pour trouver les appels que vous recherchez.

+0

Merci beaucoup. Le code ne fonctionne pas car il n'a besoin que de la fonction OpCode.ArgumentSize() pour fonctionner correctement. C'est une extension que vous avez écrite je pense. – kerem

+2

Merci beaucoup d'avoir posté ce code; c'est très utile. Cependant, il y a un bug. La taille de l'argument de l'instruction switch (OperandType.InlineSwitch) n'est pas constante, donc votre fonction ArgumentSize() ne peut pas renvoyer la valeur correcte. La valeur correcte est 4 * (x + 1) où x est l'entier de 32 bits qui suit l'opcode. – Timwi

+1

Alternativement, vous pouvez utiliser une méthode connue pour fonctionner: http://evain.net/blog/articles/2009/04/30/reflection-based-cil-reader –

-1

@Ian G: J'ai compilé la liste de ECMA 335 et découvert que je peux utiliser

List<MethodInfo> mis = 
    myObject.GetType().GetMethods().Where((MethodInfo mi) => 
     { 
      mi.GetCustomAttributes(typeof(MyAttribute), true).Length > 0; 
     } 
    ).ToList(); 
foreach(MethodInfo mi in mis) 
{ 
    List<Instruction> lst = ReflectionHelper.ReadIL(mi); 
    ... find useful opcode 
    FieldInfo fi = mi.Module.ResolveField((int)usefulOpcode.Argument); 
    object o = fi.GetValue(myObject); 
    ... 
} 

Et la liste longueur opcode est ici, si quelqu'un en a besoin:

Dictionary<OperandType, int> operandSizes 
= new Dictionary<OperandType, int>() 
{ 
    {OperandType.InlineBrTarget, 4}, 
    {OperandType.InlineField, 4}, 
    {OperandType.InlineI, 4}, 
    {OperandType.InlineI8,8}, 
    {OperandType.InlineMethod,4}, 
    {OperandType.InlineNone,0}, 
    {OperandType.InlineR,8}, 
    {OperandType.InlineSig,4}, 
    {OperandType.InlineString,4}, 
    {OperandType.InlineSwitch,4}, 
    {OperandType.InlineTok,4}, 
    {OperandType.InlineType,4}, 
    {OperandType.InlineVar,2}, 
    {OperandType.ShortInlineBrTarget,1}, 
    {OperandType.ShortInlineI,1}, 
    {OperandType.ShortInlineR,4}, 
    {OperandType.ShortInlineVar,1} 
}; 
+1

Il y a un bug significatif dans ceci; la taille de l'opérande pour InlineSwitch est incorrecte. Voir mon commentaire sur la réponse acceptée pour plus de détails. – Timwi

4

Voici une version complète de la bonne réponse. Cela utilise du matériel provenant d'autres réponses, mais incorpore un correctif important que personne d'autre n'a repéré.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Reflection; 
using System.Reflection.Emit; 

namespace Timwi.ILReaderExample 
{ 
    public class ILReader 
    { 
     public class Instruction 
     { 
      public int StartOffset { get; private set; } 
      public OpCode OpCode { get; private set; } 
      public long? Argument { get; private set; } 
      public Instruction(int startOffset, OpCode opCode, long? argument) 
      { 
       StartOffset = startOffset; 
       OpCode = opCode; 
       Argument = argument; 
      } 
      public override string ToString() 
      { 
       return OpCode.ToString() + (Argument == null ? string.Empty : " " + Argument.Value); 
      } 
     } 

     private Dictionary<short, OpCode> _opCodeList; 

     public ILReader() 
     { 
      _opCodeList = typeof(OpCodes).GetFields().Where(f => f.FieldType == typeof(OpCode)).Select(f => (OpCode) f.GetValue(null)).ToDictionary(o => o.Value); 
     } 

     public IEnumerable<Instruction> ReadIL(MethodBase method) 
     { 
      MethodBody body = method.GetMethodBody(); 
      if (body == null) 
       yield break; 

      int offset = 0; 
      byte[] il = body.GetILAsByteArray(); 
      while (offset < il.Length) 
      { 
       int startOffset = offset; 
       byte opCodeByte = il[offset]; 
       short opCodeValue = opCodeByte; 
       offset++; 

       // If it's an extended opcode then grab the second byte. The 0xFE prefix codes aren't marked as prefix operators though. 
       if (opCodeValue == 0xFE || _opCodeList[opCodeValue].OpCodeType == OpCodeType.Prefix) 
       { 
        opCodeValue = (short) ((opCodeValue << 8) + il[offset]); 
        offset++; 
       } 

       OpCode code = _opCodeList[opCodeValue]; 

       Int64? argument = null; 

       int argumentSize = 4; 
       if (code.OperandType == OperandType.InlineNone) 
        argumentSize = 0; 
       else if (code.OperandType == OperandType.ShortInlineBrTarget || code.OperandType == OperandType.ShortInlineI || code.OperandType == OperandType.ShortInlineVar) 
        argumentSize = 1; 
       else if (code.OperandType == OperandType.InlineVar) 
        argumentSize = 2; 
       else if (code.OperandType == OperandType.InlineI8 || code.OperandType == OperandType.InlineR) 
        argumentSize = 8; 
       else if (code.OperandType == OperandType.InlineSwitch) 
       { 
        long num = il[offset] + (il[offset + 1] << 8) + (il[offset + 2] << 16) + (il[offset + 3] << 24); 
        argumentSize = (int) (4 * num + 4); 
       } 

       // This does not currently handle the 'switch' instruction meaningfully. 
       if (argumentSize > 0) 
       { 
        Int64 arg = 0; 
        for (int i = 0; i < argumentSize; ++i) 
        { 
         Int64 v = il[offset + i]; 
         arg += v << (i * 8); 
        } 
        argument = arg; 
        offset += argumentSize; 
       } 

       yield return new Instruction(startOffset, code, argument); 
      } 
     } 
    } 

    public static partial class Program 
    { 
     public static void Main(string[] args) 
     { 
      var reader = new ILReader(); 
      var module = typeof(Program).Module; 
      foreach (var instruction in reader.ReadIL(typeof(Program).GetMethod("Main"))) 
      { 
       string arg = instruction.Argument.ToString(); 
       if (instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldflda || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldsflda || instruction.OpCode == OpCodes.Stfld) 
        arg = module.ResolveField((int) instruction.Argument).Name; 
       else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Calli || instruction.OpCode == OpCodes.Callvirt) 
        arg = module.ResolveMethod((int) instruction.Argument).Name; 
       else if (instruction.OpCode == OpCodes.Newobj) 
        // This displays the type whose constructor is being called, but you can also determine the specific constructor and find out about its parameter types 
        arg = module.ResolveMethod((int) instruction.Argument).DeclaringType.FullName; 
       else if (instruction.OpCode == OpCodes.Ldtoken) 
        arg = module.ResolveMember((int) instruction.Argument).Name; 
       else if (instruction.OpCode == OpCodes.Ldstr) 
        arg = module.ResolveString((int) instruction.Argument); 
       else if (instruction.OpCode == OpCodes.Constrained || instruction.OpCode == OpCodes.Box) 
        arg = module.ResolveType((int) instruction.Argument).FullName; 
       else if (instruction.OpCode == OpCodes.Switch) 
        // For the 'switch' instruction, the "instruction.Argument" is meaningless. You'll need extra code to handle this. 
        arg = "?"; 
       Console.WriteLine(instruction.OpCode + " " + arg); 
      } 
      Console.ReadLine(); 
     } 
    } 
} 
+1

Le long? pour l'argument n'est pas vraiment élégant :) –

+0

Je pense que c'est parfaitement élégant. C'est une valeur optionnelle. La seule chose qui n'est pas élégante est la façon dont elle essaie (et échoue) d'utiliser ce champ Argument pour l'argument de l'instruction 'switch', qui ne rentre pas dans le temps. – Timwi

Questions connexes