2009-09-16 16 views
5

En .NET, usando la reflexión ¿cómo puedo obtener las variables de clase que se usan en un método?¿Cómo puedo obtener campos usados ​​en un método (.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 
    } 
} 

Nota: GetClassVariablesInMethod (M1 MethodInfo) devuelve las variables B y C. Por variables me refiero a los Parámetros Value y/o Type y Constructor de esa variable específica.

+0

No entiendo lo que está tratando de hacer. ¿Por qué necesitas reflexión? Con "variables de clase", ¿te refieres a campos? Puede obtener fácilmente la instancia actual de un determinado campo, pero no los argumentos del constructor que se utilizan para crearlo. ¿Por qué necesitas esto? –

+0

Por variables de clase me refiero a campos con ámbito de clase que son clases. Estoy pensando en declarar un atributo para algunos métodos que necesita cosas especiales que se hagan de acuerdo con las variables que utiliza desde su clase principal. La instancia actual de un determinado campo puede funcionar para mí. – kerem

Respuesta

9

Hay muchas respuestas diferentes, pero como ninguna me atrae, aquí está la mía. Está usando mi Reflection based IL reader.

Aquí es un método de recuperación de todos los campos utilizados por un método:

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

Reflection es principalmente una API para inspeccionar metadatos. Lo que intenta hacer es inspeccionar IL sin procesar, que no es una función de reflexión admitida. La reflexión simplemente devuelve IL como un byte sin formato [] que debe inspeccionarse manualmente.

+1

no muy detallado ... –

+1

@romkyns, tampoco es su comentario. – JaredPar

+0

Independientemente de los comentarios de romkyns, su respuesta no es muy detallada. Otras dos respuestas aquí (la mía y la de Jb Evain) tienen una solución completa. – Timwi

0

Debe obtener MethodInfo. Llame a GetMethodBody() para obtener la estructura del cuerpo del método y luego llame a GetILAsByteArray sobre eso. El convertirá ese conjunto de bytes en una secuencia de IL comprensible.

En términos generales

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

donde OpCodeList se construye a través de

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

A continuación, puede averiguar qué instrucciones son IL llamadas propiedad o variable ups mirada miembros o lo que sea que se requieren y resolver entonces a través de GetType() .Module.ResolveField.

(código de advertencia de más o menos trabajo, pero fue arrancado de un proyecto más grande que hice así que tal vez le faltan detalles menores).

Editar: tamaño argumento es un método de extensión de código de operación que sólo utiliza una tabla de consulta que hacer encontrar el valor apropiado

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]; 
} 

Encontrará tamaños en ECMA 335 que también necesitará busque los OpCodes para encontrar qué OpCodes debe buscar para encontrar las llamadas que está buscando.

+0

Muchas gracias. El código no funciona, ya que solo necesita la función OpCode.ArgumentSize() para funcionar correctamente. Esa es una extensión que escribiste, creo. – kerem

+2

Muchas gracias por publicar este código; es muy útil. Sin embargo, hay un error en eso. El tamaño del argumento de la instrucción switch (OperandType.InlineSwitch) no es constante, por lo que su función ArgumentSize() no puede devolver el valor correcto. El valor correcto es 4 * (x + 1) donde x es el entero de 32 bits que sigue al código de operación. – Timwi

+1

Alternativamente, puede utilizar un método que se sabe que funciona: http://evain.net/blog/articles/2009/04/30/reflection-based-cil-reader –

-1

@Ian G: He compilado la lista de ECMA 335 y descubierto que puedo usar

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

Y la lista longitud de código de operación es aquí, si alguien lo necesita:

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

Existe un error significativo en esto; el tamaño del operando para InlineSwitch es incorrecto. Ver mi comentario sobre la respuesta aceptada para más detalles. – Timwi

4

Aquí hay una versión completa de la respuesta correcta. Esto usa material de otras respuestas, pero incorpora una corrección de errores importante que nadie más detectó.

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

¿El largo? para el argumento no es realmente elegante :) –

+0

Creo que es perfectamente elegante. Es un valor opcional. Lo único que no es elegante es cómo intenta (y falla) usar ese campo Argumento para el argumento de la instrucción 'cambiar', que no cabe en mucho tiempo. – Timwi

Cuestiones relacionadas