2

明確にするために、これはこの質問の複製ではありません。明らかに、演算子を使用して変数またはパラメーターの名前を取得できます。そんなこと知ってる。しかし、メソッドに渡された変数の元の名前を取得する方法はありますか? 現在、私はこのようにする必要があります: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:

人々は、私が実際に達成しようとしていることについて尋ねました。上記の最初の方法と非常に似ていますが、実際に私が使用している方法は次のとおりです。

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

しかし、変数の名前を繰り返し提供することを避ける方法を探しています。

4

3 に答える 3

2

あなたが探しているものは、パラメータ名を追加で渡すよりもはるかに遅くなります。

しかし、回避策は可能です。私はあなたの問題に夢中になり、何かを見つけました。制限があります。ローカル変数のみを扱うなど。(ただし、他のケースを解決するために拡張できます)。また、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 として渡されます

于 2019-03-18T21:50:16.727 に答える