如何获取方法 (.NET) 中使用的字段?

发布于 2024-08-05 03:38:58 字数 347 浏览 7 评论 0原文

在 .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 变量。 我所说的变量是指该特定变量的值和/或类型和构造函数参数。

In .NET, using reflection how can I get class variables that are used in a method?

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

Note:
GetClassVariablesInMethod(M1 MethodInfo) returns B and C variables.
By variables I mean Value and/or Type and Constructor Parameters of that specific variable.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

太阳哥哥 2024-08-12 03:38:58

有很多不同的答案,但由于没有一个答案对我有吸引力,所以这是我的。它使用我的基于反射的 IL 阅读器

这是一个检索方法使用的所有字段的方法:

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

There's a lot of different answers, but as not a single one appeals to me, here's mine. It's using my Reflection based IL reader.

Here's a method retrieving all the fields used by a method:

static IEnumerable<FieldInfo> GetUsedFields (MethodInfo method)
{
    return (from instruction in method.GetInstructions ()
           where instruction.OpCode.OperandType == OperandType.InlineField
           select (FieldInfo) instruction.Operand).Distinct ();
}
宣告ˉ结束 2024-08-12 03:38:58

这是正确答案的完整版本。这使用了其他答案中的材料,但包含了一个其他人没有发现的重要错误修复。

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

Here's a complete version of the correct answer. This uses material from other answers, but incorporates an important bugfix which no-one else spotted.

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();
        }
    }
}
廻憶裏菂餘溫 2024-08-12 03:38:58

您需要获取 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 您还需要查看其中的操作码来查找要搜索哪些操作码来查找调用你正在寻找。

You need to get the MethodInfo. Call GetMethodBody() to get the method body structure and then call GetILAsByteArray on that. The convert that byte array into a stream of comprehensible IL.

Roughly speaking

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

where OpCodeList is constructed via

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

You can then work out which instructions are IL property calls or member variable look ups or whatever you require and resolve then via GetType().Module.ResolveField.

(Caveat code above more or less work but was ripped from a bigger project I did so maybe missing minor details).

Edit: Argument size is an extension method on OpCode that just uses a look up table to do find the appropriate value

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

You'll find sizes in ECMA 335 which you'll also need to look at for the OpCodes to find which OpCodes you to search for to find the calls you are looking for.

幽梦紫曦~ 2024-08-12 03:38:58

反射主要是一个用于检查元数据的 API。您想要做的是检查原始 IL,这不是反射支持的功能。反射仅将 IL 作为原始 byte[] 返回,必须手动检查。

Reflection is primarily an API for inspecting metadata. What you're trying to do is inspect raw IL which is not a supported function of reflection. Reflection just returns IL as a raw byte[] which must be manually inspected.

离线来电— 2024-08-12 03:38:58

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

@Ian G: I have compiled the list from ECMA 335 and found out that I can use

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

And the opcode length list is here, if anyone needs it:

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}
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文