获取作为参数传递的变量的原始名称?

时间:2019-03-18 07:13:29

标签: c# reflection nameof

请注意,这不是this question的副本。显然,我可以使用nameof运算符来获取变量或参数的名称。我知道。但是,有什么办法可以获取传递给方法的变量的原始名称?目前,我必须这样做:

static void Foo(string someVariable, string variableName)
{
    if (!FulfilsCondition(someVariable))
        Console.WriteLine($"{variableName} is bad!");

    // More code
}

我这样称呼它:

string bar = string.Empty;
Foo(bar, nameof(bar));    // Or...
//Foo(bar, "bar");

但是我正在寻找一种避免重复提供变量名称的方法,而是使用类似的方法:

Foo(bar);

在这种情况下,Foo在哪里:

static void Foo(string someVariable)
{
    string variableName = GetOriginalVariableName(someVariable);
    //  Is this possible? ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 
    if (!FulfilsCondition(someVariable))
        Console.WriteLine($"{variableName} is bad!");

    // More code
}

在.NET中可以实现吗?


更新:

我没有想到传递给Foo的内容可能是表达式,而不是其他人在评论中建议的变量。现在考虑一下,似乎不可能实现_除非我可以(以某种方式)保证总会有一个变量? TBH听起来很遥远,但也许那里有解决方案。

更新#2:

人们问我实际上想要达到的目标。好吧,它与上面的第一种方法非常相似,但是如果有帮助,这是我正在使用的 actual 方法:

static bool ExceedsLimit(string s, int maxLength, string variableName,
                         out string errorMessage)
{
    if (s.Length > maxLength)
    {
        errorMessage = $"'{variableName}' must be {maxLength} characters at most.";
        return true;
    }

    errorMessage = null;
    return false;
}

我正在像这样使用它:

static bool TestMethod(out bool failReason)
{
    if (ExceedsLimit(obj.Prop1, 100, nameof(obj.Prop1), out failReason)) return false;
    if (ExceedsLimit(obj.Prop2, 50, nameof(obj.Prop2), out failReason)) return false;
    if (ExceedsLimit(obj.Prop3, 80, nameof(obj.Prop3), out failReason)) return false;
    // ...
}
  

但是我正在寻找一种避免重复提供变量名称的方法。

3 个答案:

答案 0 :(得分:1)

您正在寻找的东西比另外传递参数名称要慢得多。

但是解决方法是可能的。我为你的问题发疯了,发现了一些东西。它有限制。如只处理局部变量。 (但可以扩展以解决其他情况)。并且它需要pdb文件和ildasm工具。 (这似乎是获得IL的最简单方法,但也许可以使用框架功能来获得它)。而且非常慢。但有效) 只需调用ParamNameHelper.GetOriginalVariableName(string paramName)。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;

namespace TestParamHelper
{
    class Program
    {
        static void Main(string[] args)
        {
            new CallingClass().CallTargetMethod();
        }
    }

    public class CallingClass
    {
        public void CallTargetMethod()
        {
            var s = "str";
            var i = 5;
            new TargetClass().TargetMethod(s, i);
        }
    }

    public class TargetClass
    {
        public void TargetMethod(string strArg, int intArg)
        {
            var paramName = nameof(strArg);

            // HERE IT IS!!!
            var originalName = ParamNameHelper.GetOriginalVariableName(paramName);

            Console.WriteLine($"{originalName} is passed as {paramName}");
        }
    }

    public static class ParamNameHelper
    {
        public static string GetOriginalVariableName(string paramName)
        {
            var stackTrace = new StackTrace(true);

            var targetMethod = stackTrace.GetFrame(1).GetMethod();
            var paramIndex = targetMethod.GetParameters().ToList().FindIndex(p => p.Name.Equals(paramName));

            var callingMethod = stackTrace.GetFrame(2).GetMethod();
            var il = callingMethod.GetMethodBodyIL();

            var localIndex = il
                .TakeWhile(s => !s.Contains($"{targetMethod.DeclaringType.FullName}::{targetMethod.Name}"))
                .Reverse()
                .TakeWhile(s => s.Contains("ldloc"))
                .Reverse()
                .ElementAt(paramIndex)
                .Split('.')
                .Last();

            return il
                .SkipWhile(s => !s.Contains("locals init"))
                .TakeWhile(s => s.Contains(",") || s.Contains(")"))
                .First(s => s.Contains($"[{localIndex}]"))
                .Replace(")", "")
                .Replace(",", "")
                .Split(' ')
                .Last();
        }
    }

    internal static class MethodBaseExtensions
    {
        // improve providing location, may be via config
        private static readonly string ildasmLocation = Path.GetFullPath(@"C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.7.2 Tools\ildasm.exe");

        internal static IEnumerable<string> GetMethodBodyIL(this MethodBase method)
        {
            var assemblyLocation = method.DeclaringType.Assembly.Location;
            var ilLocation = $"{assemblyLocation}.il";

            Process.Start(new ProcessStartInfo(ildasmLocation, $"{assemblyLocation} /output:{ilLocation}") { UseShellExecute = false })
                .WaitForExit();

            var il = File.ReadAllLines(ilLocation)
                .SkipWhile(s => !s.Contains(method.Name))
                .Skip(2)
                .TakeWhile(s => !s.Contains($"end of method {method.DeclaringType.Name}::{method.Name}"));

            File.Delete(ilLocation);

            return il;
        }
    }
}

输出:s作为strArg传递

答案 1 :(得分:0)

否,这是不可能的,因为方法无法知道其参数名称在何处。 Aka Foo无法知道它是被称为Foo(bar)还是Foo(baz)。您所做的就是使用CallerMemberName获取调用它的方法的名称。例如:

static void Foo(string someVariable, [CallerMemberName] string methodName = "")
{
    if (!FulfilsCondition(someVariable))
        Console.WriteLine($"{methodName} passed a bad paramter!");

    // More code
}

static void BadMethod()
{
    string wrong = "";
    Foo(wrong);
}

会打印:

BadMethod passed a bad paramter!

答案 2 :(得分:0)

您可以为此使用Mono.Cecil。您还需要this answerMonoCecilReader文件中的.pdb和其他一些类型来获取局部变量的名称。

用{p>补充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'

上述解决方案主要是可能性的证明。它比简单地传递变量名称要慢得多。

相关问题