代码之家  ›  专栏  ›  技术社区  ›  Ahmed Abdelhameed

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

  •  3
  • Ahmed Abdelhameed  · 技术社区  · 7 年前

    不是 副本 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中是可以实现的吗?


    更新:

    我没想到有可能 福

    更新2:

    事实上 实际的 我正在使用的方法如果有帮助的话:

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

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

    0 回复  |  直到 7 年前
        1
  •  2
  •   CSDev    7 年前

    您要寻找的工作速度要比另外传递param名称慢得多。

    打个电话就行了ParamNameHelper.GetOriginalVariableName(字符串参数名)。

    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传递

        2
  •  1
  •   Rain336    7 年前

    不,这是不可能的,因为一个方法无法知道它的参数名在哪里。又名 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!
    
        3
  •  0
  •   CSDev    7 年前

    你可以用 Mono.Cecil 为此目的。你也需要 MonoCecilReader this answer 和 .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'
    

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