在.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 :(得分:10)
有很多不同的答案,但由于没有一个人对我有吸引力,所以这是我的。它正在使用我的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 :(得分: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();
}
}
}
答案 2 :(得分:1)
您需要获取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找到尺寸,您还需要查看操作码以查找要搜索的操作码以查找您要查找的电话。
答案 3 :(得分:-1)
Reflection主要是用于检查元数据的API。你要做的是检查原始IL,这不是反射支持的功能。反射只返回IL作为原始字节[],必须手动检查。
答案 4 :(得分:-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}
};