从扩展方法中获取原始变量名

本文关键字:原始 变量名 获取 扩展 方法 | 更新日期: 2023-09-27 18:00:06

我们目前正在开发一个日志解决方案,并实现了一个名为"Log"的扩展方法。在写入日志文件时,理想情况下,我们希望写入原始变量名(而不是扩展方法中使用的变量名)。

我们目前要做的是:

public void DoSomeWork()
{
    String testString = "Hey look I'm a string!";
    testString.Log("testString value");
}

采用扩展方法:

public static String Log(this String valueToStore, String name)
{
    // The logging code which uses the 'name' parameter
}

这里的问题是,在较长的代码行上阅读变得困难,并且看起来是集群的。理想的情况是:

public void DoSomeWork()
{
    String testString = "Hey look I'm a string!";
    testString.Log();
}

使用扩展方法:

public static String Log(this String valueToStore)
{
    // The logging code which is able to retrieve the 
    // value 'testString' from the 'valueToStore' value
}

使用"反射"可以做到这一点吗?我知道nameof选项,但它只在扩展方法中使用时返回字符串"valueToStore"。

从扩展方法中获取原始变量名

好吧,简短的答案是否定的。变量名不能保证在编译后以不变的形式保持不变。这些信息必须以某种方式保持不变(例如使用nameof())。此外,变量名称可能不存在("test".GetVarName())。

答案很长:是的,可能,但这是我一生中创造的最荒谬的事情之一:

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
namespace Test1
{
    class Program
    {
        static void Main(string[] args)
        {
            var myVarName = "test";
            myVarName.Test();
            Console.ReadKey();
        }
    }
    static class Extensions
    {
        public static void Test(
            this string str,
            [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
            [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
            [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0
        )
        {
            var relevantLine = File.ReadAllLines(sourceFilePath)[sourceLineNumber-1];
            var currMethodName = MethodInfo.GetCurrentMethod().Name;
            var callIndex = relevantLine.IndexOf(currMethodName + "()");
            var sb = new Stack<char>();
            for (var i = callIndex - 2; i >= 0; --i)
            {
                if (Char.IsLetterOrDigit(relevantLine[i]))
                {
                    sb.Push(relevantLine[i]);
                }
            }
            Console.WriteLine(new String(sb.ToArray()));
        }
    }
}

C#10具有CallerArgumentExpressionAttribute,它将执行

public static void PrintNameAndValue(
    this object obj,
    [System.Runtime.CompilerServices.CallerArgumentExpression("obj")] string callerExp = ""
    )
{
    Console.WriteLine(callerExp + " = " + obj.ToString());
}

它将捕获传递的整个表达式:

public void TestPrintNameAndValue()
{
    string mystring = "test";
    int myint = 5;
    mystring.PrintNameAndValue();             // mystring = test
    myint.PrintNameAndValue();                // myint = 5
    (myint + 10).PrintNameAndValue();         // myint + 10 = 15
    mystring.ToUpper().PrintNameAndValue();   // mystring.ToUpper() = TEST
}

您可以使用Expression来实现这一点,但就性能而言,它可能不是最佳选项:

public static void Log<T>(Expression<Func<T>> expr)
{
    var memberExpr = expr.Body as MemberExpression;
    if (memberExpr == null)
        return;
    var varName = memberExpr.Member.Name;
    var varData = expr.Compile()();
    // actual logging
    ...
}

用法:

var test = "Foo";
Log(() => test);

或者,如果您使用的是C#6.0,那么使用nameof运算符会更好一些

test.Log(nameof(test));

更好的解决方案是利用编译器功能(特别是"Roslyn"编译器),并在编译时提供成员名称。

不是一个真正的答案,更像是一个指针,但你可以尝试用你正在使用的应用程序(例如visualstudio)做一些事情,而不是在代码中做。我的意思是让它重写所有看起来像[variable].Log();[variable].Log([variable])

我敢肯定,在编译之前,一定会有一些奇怪的宏或插件为你做这件事。