从扩展方法中获取原始变量名
Obtaining original variable name from within an extension method
我们目前正在研究日志记录解决方案,并已实现扩展方法调用 '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
选项,但只有 returns 字符串 'valueToStore' 在扩展方法中使用时。
您可以使用 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" 编译器)并在编译时提供成员名称。
不是真正的答案,更像是一个指针,但您可以尝试对您正在使用的应用程序做一些事情(例如 visual studio),而不是在代码中做。我的意思是让它将所有看起来像 [variable].Log();
的东西重写为 [variable].Log([variable])
我很确定必须有一些奇怪的宏或插件在编译之前为您完成此操作。
好吧,简短的回答是否定的。不保证变量名在编译后以不变的形式保留。该信息必须以某种方式持久化(例如通过使用 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
}
我们目前正在研究日志记录解决方案,并已实现扩展方法调用 '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
选项,但只有 returns 字符串 'valueToStore' 在扩展方法中使用时。
您可以使用 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" 编译器)并在编译时提供成员名称。
不是真正的答案,更像是一个指针,但您可以尝试对您正在使用的应用程序做一些事情(例如 visual studio),而不是在代码中做。我的意思是让它将所有看起来像 [variable].Log();
的东西重写为 [variable].Log([variable])
我很确定必须有一些奇怪的宏或插件在编译之前为您完成此操作。
好吧,简短的回答是否定的。不保证变量名在编译后以不变的形式保留。该信息必须以某种方式持久化(例如通过使用 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
}