您可以Mono.Cecil
用于此目的。您还需要此答案和文件中的MonoCecilReader
一些其他类型来获取局部变量的名称。.pdb
修改MethodDefinitionExtensions
为
public static Instruction GetInstruction(this MethodDefinition method, int offset) =>
method
.Body
.Instructions
.SingleOrDefault(i => i.Offset == offset);
创建ResolvedArgument
类:
public class ResolvedArgument
{
public string Argument { get; }
public string Parameter { get; }
public ResolvedArgument(string argument, string parameter) =>
(Argument, Parameter) = (argument, parameter);
public override string ToString() =>
$"'{Argument}' passed for '{Parameter}'";
}
然后使用方法创建静态类VariableHelper
:
public static ResolvedArgument ResolveArgument(object parameter)
{
var trace = new StackTrace();
var frame = trace.GetFrame(1);
var method = frame.GetMethod().GetMethodDefinition();
var arg = GetParameter(frame, method);
frame = trace.GetFrame(2);
return GetPassedValue(frame, method, arg);
}
在哪里GetParameter
:
private static ParameterDefinition GetParameter(StackFrame frame, MethodDefinition method)
{
var instruction = method.GetInstruction(frame.GetILOffset());
if (instruction.IsBoxing())
instruction = instruction.Previous;
if (!instruction.IsLoadArg())
throw new NotSupportedException($"Attempt to handle {instruction.OpCode}");
if (instruction.OpCode.Code == Code.Ldarg_S)
return (ParameterDefinition)instruction.Operand;
var index = instruction.GetArgIndex(!method.IsStatic);
return method.Parameters[index];
}
并且GetPassedValue
是:
private static ResolvedArgument GetPassedValue(StackFrame frame, MethodDefinition method, ParameterDefinition parameter)
{
var info = frame.GetMethod();
var caller = info.GetMethodDefinition();
var instruction = caller.GetInstruction(frame.GetILOffset());
while (instruction != null)
{
if (instruction.IsCall() &&
instruction.Operand is MethodDefinition md &&
md.FullName.Equals(method.FullName))
break;
instruction = instruction.Previous;
}
if (instruction == null)
throw new Exception("Not supposed to get here.");
var il = caller
.Body
.Instructions
.TakeWhile(i => i.Offset != instruction.Offset)
.Reverse()
.Where(i => !i.IsBoxing() && (caller.IsStatic || i.OpCode.Code != Code.Ldarg_0))
.TakeWhile(i =>i.IsLoad())
.Reverse()
.ToList();
if (il.Count != method.Parameters.Count)
throw new NotSupportedException("Possible attempt to pass an expression");
instruction = il[parameter.Index];
var name = "<failed to resolve>";
if (instruction.IsLoadArg())
{
var index = instruction.GetArgIndex(!caller.IsStatic);
name = caller.Parameters.Single(p => p.Index == index).Name;
}
if (instruction.IsLoadField())
name = ((FieldDefinition)instruction.Operand).Name;
if (instruction.IsLoadLoc())
{
var index = instruction.GetLocIndex();
var locals = new MonoCecilReader().Read(info);
name = locals.Single(loc => loc.Index == index).Name;
}
return new ResolvedArgument(name, parameter.Name);
}
扩展方法Instruction
有:
internal static class InstructionExtensions
{
public static bool IsCall(this Instruction instruction)
{
var code = instruction.OpCode.Code;
return code == Code.Call ||
code == Code.Callvirt;
}
public static bool IsBoxing(this Instruction instruction) =>
instruction.OpCode.Code == Code.Box;
public static bool IsLoadArg(this Instruction instruction)
{
var code = instruction.OpCode.Code;
return code == Code.Ldarg_0 ||
code == Code.Ldarg_1 ||
code == Code.Ldarg_2 ||
code == Code.Ldarg_3 ||
code == Code.Ldarg_S;
}
public static bool IsLoadLoc(this Instruction instruction)
{
var code = instruction.OpCode.Code;
return code == Code.Ldloc_0 ||
code == Code.Ldloc_1 ||
code == Code.Ldloc_2 ||
code == Code.Ldloc_3 ||
code == Code.Ldloc_S;
}
public static bool IsLoadField(this Instruction instruction)
{
var code = instruction.OpCode.Code;
return code == Code.Ldfld ||
code == Code.Ldsfld;
}
public static int GetArgIndex(this Instruction instruction, bool isInstance)
{
if (instruction.OpCode.Code == Code.Ldarg_S)
return ((ParameterDefinition)instruction.Operand).Index;
var index = -1;
var code = instruction.OpCode.Code;
if (code == Code.Ldarg_0)
index = 0;
else if (code == Code.Ldarg_1)
index = 1;
else if (code == Code.Ldarg_2)
index = 2;
else if (code == Code.Ldarg_3)
index = 3;
if (index != -1 && isInstance)
index--;
return index;
}
public static int GetLocIndex(this Instruction instruction)
{
if (instruction.OpCode.Code == Code.Ldloc_S)
return ((VariableDefinition)instruction.Operand).Index;
var code = instruction.OpCode.Code;
if (code == Code.Ldloc_0)
return 0;
if (code == Code.Ldloc_1)
return 1;
if (code == Code.Ldloc_2)
return 2;
if (code == Code.Ldloc_3)
return 3;
return -1;
}
public static bool IsLoad(this Instruction instruction) =>
instruction.IsLoadArg() ||
instruction.IsLoadLoc() ||
instruction.IsLoadField();
}
用法:
class Program
{
private static readonly Guid sFld1 = default(Guid);
private readonly DateTime iFld1 = default(DateTime);
private static readonly Guid sFld2 = default(Guid);
private readonly DateTime iFld2 = default(DateTime);
static void Main(string[] args)
{
new Program().Run("_1", "_2");
}
private void Run(string arg1, string arg2)
{
int loc1 = 42;
int loc2 = 24;
Console.WriteLine("\tFirst call");
Method(p1: loc1, p2: arg1, p3: sFld1, p4: iFld1);
Console.WriteLine("\tSecond call");
Method(p1: loc2, p2: arg2, p3: sFld2, p4: iFld2);
}
private void Method(int p1, string p2, object p3, DateTime p4)
{
Console.WriteLine(VariableHelper.ResolveArgument(p1));
Console.WriteLine(VariableHelper.ResolveArgument(p2));
Console.WriteLine(VariableHelper.ResolveArgument(p3));
Console.WriteLine(VariableHelper.ResolveArgument(p4));
}
}
给出:
First call
'loc1' passed for 'p1'
'arg1' passed for 'p2'
'sFld1' passed for 'p3'
'iFld1' passed for 'p4'
Second call
'loc2' passed for 'p1'
'arg2' passed for 'p2'
'sFld2' passed for 'p3'
'iFld2' passed for 'p4'
上述解决方案主要是一种可能性的证明。它比简单地传递变量名要慢得多。