Получить оригинальное имя переменной, переданной в качестве параметра? - PullRequest
1 голос
/ 18 марта 2019

Для ясности, это Не дубликат этого вопроса . Очевидно, я могу использовать оператор 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, может быть выражением, а не переменной, как другие предлагали в комментариях. Думая об этом сейчас, кажется, невозможно достичь того, что _ если я (каким-то образом) могу гарантировать, что всегда будет переменная? Звучит как длинный выстрел, ТБХ, но, возможно, там есть решение.

Обновление № 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;
    // ...
}

Но я ищу способ избежать повторного предоставления имени переменной.

Ответы [ 2 ]

2 голосов
/ 19 марта 2019

То, что вы ищете, будет работать намного медленнее, чем дополнительная передача имени параметра.

Но обходные пути возможны.Я сходил с ума от твоей проблемы и кое-что узнал.У него есть ограничения.Например, иметь дело только с локальными переменными.(Но может быть расширен для решения других случаев).И это нуждается в файлах pdb и инструменте ildasm.(Казалось, это самый простой способ получить IL, но, возможно, его можно получить с помощью функциональности фреймворка).И это ужасно медленно.Но это работает) Просто вызовите ParamNameHelper.GetOriginalVariableName (строка 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 голос
/ 18 марта 2019

Нет, это невозможно, потому что метод не может узнать, где имена его аргументов.Ака 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!
...