2009-09-16 2 views
5

.NET에서 리플렉션을 사용하면 메소드에서 사용되는 클래스 변수를 어떻게 얻을 수 있습니까?메서드에서 사용한 필드를 가져 오는 방법 (.NET)?

예 :

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

참고 GetClassVariablesInMethod (M1 MethodInfo) B 및 C를 반환 변수. 변수 란 해당 변수의 값 및/또는 유형 및 생성자 매개 변수를 의미합니다.

+0

:

는 다음의 방법으로 사용되는 모든 필드를 검색하는 방법입니다. 왜 당신은 반성이 필요합니까? "클래스 변수"를 사용하면 필드를 의미합니까? 특정 필드의 현재 인스턴스를 쉽게 가져올 수 있지만 인스턴스를 생성하는 데 사용되는 생성자 인수는 가져올 수 없습니다. 왜 이것을 필요로합니까? –

+0

클래스 변수 란 클래스라는 클래스 범위 필드를 의미합니다. 부모 클래스에서 사용하는 변수에 따라 수행해야 할 특별한 작업이 필요한 일부 메서드에 대한 특성을 선언 할 생각입니다. 특정 필드의 현재 인스턴스가 나를 위해 작동 할 수 있습니다. – kerem

답변

9

다른 답변이 많이 있지만, 저에게 호소력이있는 것은 아닙니다. 여기에 있습니다. 내 Reflection based IL reader을 사용 중입니다. 난 당신이 뭘 하려는지 이해가 안

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

반사는 기본적으로 메타 데이터를 검사하기위한 API입니다. 당신이하려고하는 것은 원시 일리노이를 조사하는 것인데 이것은 반영의 기능이 아닙니다. 리플렉션은 IL을 원시 바이트 []로 반환합니다.이 바이트는 수동으로 검사해야합니다.

+1

매우 자세히 ... –

+1

@romkyns, 어느 쪽 코멘트도 아닙니다. – JaredPar

+0

romkyns의 의견에 관계없이 귀하의 답변은 실제로 매우 상세하지 않습니다. 여기 두 개의 다른 답변 (광산 및 Jb Evain)은 완벽한 솔루션을 제공합니다. – Timwi

0

MethodInfo를 가져와야합니다. GetMethodBody() 메서드를 호출하여 메서드 본문 구조를 가져온 다음 GetILAsByteArray를 호출합니다. 해당 바이트 배열을 이해 가능한 IL 스트림으로 변환합니다.

대략 OpCodeList 당신은 당신이 필요로 GetType을 통해 다음 해결 어떤 IL 속성 호출 또는 멤버 변수를 살펴 업 또는있는 지침을 해결할 수

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

로 구성되어

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

말하기() .Module.ResolveField.

(더 많은 또는 더 적은 위의 경고 코드는 더 큰 프로젝트에서 찢어 졌기 때문에 약간의 세부 사항이 누락되었을 수 있음).

편집 : 인수의 크기는 당신이 또한 필요합니다 ECMA 335의 크기에를 찾을 수 있습니다

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

적절한 값을 발견 할 수있는 룩업 테이블을 사용하여 동작 코드에 확장 방법 OpCodes에서 찾고자하는 호출을 찾기 위해 검색 할 OpCode를 찾으십시오.

+0

고맙습니다. OpCode.ArgumentSize() 함수 만 제대로 작동하면 코드가 작동하지 않습니다. 그것은 내가 생각하기에 확장 한 것입니다. – kerem

+2

이 코드를 게시 해 주셔서 감사합니다. 매우 유용합니다. 그러나 거기에 버그가 있습니다. 전환 명령 (OperandType.InlineSwitch)의 인수 크기가 일정하지 않으므로 ArgumentSize() 함수가 올바른 값을 반환 할 수 없습니다. 올바른 값은 4 * (x + 1)입니다. 여기서 x는 opcode 다음의 32 비트 정수입니다. – Timwi

+1

또는 작동하는 것으로 알려진 방법을 사용할 수 있습니다. http://evain.net/blog/articles/2009/04/30/reflection-based-cil-reader –

-1

@Ian G : 나는 ECMA 335에서 목록을 컴파일 내가

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

을 사용할 수 있습니다 그리고 사람이 그것을 필요로하는 경우 옵 코드 길이 목록이 여기에 있다는 것을 발견했다 :

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

이것에 중대한 버그가 있습니다; InlineSwitch의 피연산자 크기가 잘못되었습니다. 자세한 내용은 허용 된 답변에 대한 내 의견을 참조하십시오. – Timwi

4

을 다음은 정답의 전체 버전입니다. 이것은 다른 답변의 자료를 사용하지만, 아무도 발견하지 못한 중요한 버그 수정을 통합합니다.

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

긴? 인수가 정말 우아하지 않습니다 :) –

+0

완벽하게 우아하다고 생각합니다. 선택적 값입니다. 우아하지 않은 유일한 방법은 long에 맞지 않는 'switch'명령어의 인수에 대해 Argument 필드를 사용하는 방법입니다. – Timwi