メソッド(.NET)で使用されるフィールドを取得するにはどうすればよいですか?
-
07-07-2019 - |
質問
.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変数を返します。 変数とは、その特定の変数の値および/またはタイプとコンストラクターパラメータを意味します。
解決
さまざまな答えがありますが、私にとって魅力的なものはありません。 反射ベースのILリーダーを使用しています。
メソッドで使用されるすべてのフィールドを取得するメソッドは次のとおりです。
static IEnumerable<FieldInfo> GetUsedFields (MethodInfo method)
{
return (from instruction in method.GetInstructions ()
where instruction.OpCode.OperandType == OperandType.InlineField
select (FieldInfo) instruction.Operand).Distinct ();
}
他のヒント
正解の完全なバージョンを次に示します。これは他の回答からの資料を使用しますが、他の誰も見つけられなかった重要なバグ修正を組み込みます。
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();
}
}
}
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];
}
Reflectionは、主にメタデータを検査するためのAPIです。あなたがやろうとしているのは、反射のサポート機能ではない生のILを検査することです。 Reflectionは、手動で検査する必要がある生のbyte []としてILを返すだけです。
@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}
};