【问题标题】:Obtaining original variable name from within an extension method从扩展方法中获取原始变量名
【发布时间】:2016-08-03 15:16:09
【问题描述】:

我们目前正在开发一种日志记录解决方案,并且已经实现了一个名为“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
}

这完全可以通过使用反射来实现吗?我知道nameofoption,但在扩展方法中使用时只返回字符串'valueToStore'。

【问题讨论】:

  • 我认为这是不可能的。此信息在执行时会丢失(变量甚至不是您可以反映的属性或字段)。您可以向上走堆栈帧并检查其字符串变量的调用方法,但这无济于事。顺便说一句:你对"hello world".Log() 有什么期望?
  • 扩展方法是否不需要保持与调用它的位置的链接?我知道使用常规方法不可能做到这一点,但认为扩展方法可能会提供一些额外的东西
  • 不,扩展方法只是语法糖。您的示例中的testString.Log()YourExtensionClass.Log(testString) 没有什么不同。

标签: c# reflection extension-methods


【解决方案1】:

嗯,简短的回答是否定的。不保证变量名称在编译后以不变的形式保留。该信息必须以某种方式持久化(例如通过使用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()));
        }
    }
}

【讨论】:

    【解决方案2】:

    您可以使用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”编译器)并在编译时提供成员名称。

    【讨论】:

    • 如何将用法实现为扩展方法,或者该解决方案是否会远离该方法?
    • @ChrisWright,您可以将其扩展为扩展方法,但看起来会很尴尬:test.Log(() =&gt; test)。如果您愿意,我可以修改答案以反映这一点。
    • 应该没问题,我只是将它作为标准方法粘贴并调用它以查看它是否正常工作,因此我可以从那里转换它。感谢您的回答!
    • @ChrisWright,我对代码做了一点改动,以适应非字符串类型的日志记录。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      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
      }
      

      【讨论】:

        猜你喜欢
        • 2011-06-04
        • 1970-01-01
        • 1970-01-01
        • 2011-09-13
        • 1970-01-01
        • 1970-01-01
        • 2014-02-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多