Как я могу получить поля, используемые в методе (.NET)? - PullRequest
5 голосов
/ 17 сентября 2009

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

Примечание: GetClassVariablesInMethod (M1 MethodInfo) возвращает переменные B и C. Под переменными я подразумеваю значение и / или тип и параметры конструктора этой конкретной переменной.

Ответы [ 5 ]

10 голосов
/ 30 ноября 2009

Есть много разных ответов, но поскольку ни один из них не привлекает меня, вот мой. Он использует мой IL-ридер на основе отражений .

Вот метод, извлекающий все поля, используемые методом:

static IEnumerable<FieldInfo> GetUsedFields (MethodInfo method)
{
    return (from instruction in method.GetInstructions ()
           where instruction.OpCode.OperandType == OperandType.InlineField
           select (FieldInfo) instruction.Operand).Distinct ();
}
4 голосов
/ 30 ноября 2009

Вот полная версия правильного ответа. Это использует материал из других ответов, но содержит важное исправление, которое никто не заметил.

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 голос
/ 17 сентября 2009

Вам необходимо получить MethodInfo. Вызовите GetMethodBody (), чтобы получить структуру тела метода, а затем вызовите GetILAsByteArray для этого. Преобразуйте этот байтовый массив в поток понятного IL.

Грубо говоря

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

, где OpCodeList создается через

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

Затем вы можете определить, какие инструкции являются вызовами свойств IL или поисками переменных-членов, или что вам нужно, и разрешите их затем через GetType (). Module.ResolveField.

(предостерегающий код выше более или менее работал, но был скопирован из более крупного проекта, который я сделал, поэтому, возможно, пропустил мелкие детали).

Редактировать: Размер аргумента - это метод расширения в OpCode, который просто использует справочную таблицу для поиска подходящего значения

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

Размеры вы найдете в ECMA 335 , на которые вам также нужно обратить внимание, чтобы найти коды операций, чтобы найти, какие коды операций вы ищете, чтобы найти вызовы, которые вы ищете.

0 голосов
/ 18 сентября 2009

@ 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}
};
0 голосов
/ 17 сентября 2009

Reflection - это прежде всего API для проверки метаданных. То, что вы пытаетесь сделать, это проверить необработанный IL, который не поддерживается функцией отражения. Reflection просто возвращает IL в виде необработанного байта [], который необходимо проверять вручную.

...