C#:使用函数表达式记录变量名称和值

发布于 2025-01-16 02:29:00 字数 2267 浏览 2 评论 0原文

在经常需要调试日志记录的代码中,我最终会得到大的代码块,例如:

int someVar = 1;
bool anotherVar = true;
//...
string lastVar = "foo";

// littered through the code
Log._Debug(
   "arbitrary message string", 
   $"{nameof(someVar)} = {someVar} " +
   $"{nameof(anotherVar)} = {anotherVar} " +
   // ...
   $"{nameof(lastVar)} = {lastVar} "
);

这些调试块有时可能很大(记录了 20 多个变量),并且它们可能在一个类中出现数十次,从而使整个代码完全不可读。遗憾的是它们是必要的 - 有时我们需要向用户发送调试版本(他们无法运行调试器,让他们运行调试版本并向我们发送日志会更容易)。它也是旧的代码库,这就是为什么它如此混乱的原因。

我正在尝试找到一种方法来消除代码中的调试块,只是为了让维护变得不那么令人沮丧哈哈。

在寻找更清晰的语法的过程中,我发现 https://stackoverflow.com/a/9801735 显示了如何获取成员名称来自 lambda 函数。这让我想知道,是否有可能创造出类似这样的东西......?

Log._Dump(
   "arbitrary message string", 
   () => somevar,
   () => anotherVar,
   // ...
   () => lastVar
);

因此,我尝试使用 params 创建一个方法,如下所示:

[Conditional("DEBUG")]
public static void _Dump(string message, params Func[] vars) {
                                             // ^ what <T> do I use?
}

private static string GetMemberName<T>(Expression<Func<T>> memberExpression) {
    // this would eventually return $"{memberName} = {memberValue}"
    // which, btw, I have no idea if that's even possible yet
    // but I didn't get that far as still trying to work out how to do
    // the _Dump() method params above :/

    MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
    return expressionBody.Member.Name;
}

我不知道如何使用不同返回类型的 params 函数数组。

例如,我可以将 params 设为字符串数组,然后手动为每个 lambda 执行 GetMemberName

private string NV<T>(Expression<Func<T>> memberExpression) {
  // ...code...

  return $"{memberName} = {memberValue}";
}

Log._Dump(
   "arbitrary message string", 
   NV(() => somevar),
   NV(() => anotherVar),
   // ...
   NV(() => lastVar)
);

但这又会向代码中添加样板文件,这就是我要做的试图避免。有什么方法可以让它在没有额外的 NV() 包装器的情况下工作吗?

编辑:它确实是旧的代码库,我们一直使用 .Net Framework 3.5,因此仅限于 C# 6 或类似的东西。

In code which regularly needs debug logging, I end up with large blocks of code such as:

int someVar = 1;
bool anotherVar = true;
//...
string lastVar = "foo";

// littered through the code
Log._Debug(
   "arbitrary message string", 
   
quot;{nameof(someVar)} = {someVar} " +
   
quot;{nameof(anotherVar)} = {anotherVar} " +
   // ...
   
quot;{nameof(lastVar)} = {lastVar} "
);

These debug blocks can sometimes be huge (20+ vars being logged) and they can occur dozens of times in a class making the whole thing completely unreadable. Sadly they're necessary - sometimes we need to send debug builds to users (they can't run debugger, it's easier just to get them to run the debug build and send us the logs). It's also old code base which is why it's such a freaking mess.

I'm trying to find a way to debloat the debug chunks in the code, just to make it less depressing to maintain lol.

In my quest to find cleaner syntax, I found https://stackoverflow.com/a/9801735 which shows how to get member name from a lambda function. Which made me wonder, is it possible to create something a bit like this...?

Log._Dump(
   "arbitrary message string", 
   () => somevar,
   () => anotherVar,
   // ...
   () => lastVar
);

So I tried creating a method using params as follows:

[Conditional("DEBUG")]
public static void _Dump(string message, params Func[] vars) {
                                             // ^ what <T> do I use?
}

private static string GetMemberName<T>(Expression<Func<T>> memberExpression) {
    // this would eventually return 
quot;{memberName} = {memberValue}"
    // which, btw, I have no idea if that's even possible yet
    // but I didn't get that far as still trying to work out how to do
    // the _Dump() method params above :/

    MemberExpression expressionBody = (MemberExpression)memberExpression.Body;
    return expressionBody.Member.Name;
}

I don't know how to do params array of functions with varying return types.

I could potentially just make the params a string array and do the GetMemberName manually for each lambda, for example:

private string NV<T>(Expression<Func<T>> memberExpression) {
  // ...code...

  return 
quot;{memberName} = {memberValue}";
}

Log._Dump(
   "arbitrary message string", 
   NV(() => somevar),
   NV(() => anotherVar),
   // ...
   NV(() => lastVar)
);

But that's adding boilerplate to the code again which is what I'm trying to avoid. Is there any way I can get it working without that extra NV() wrapper?

EDIT: It's really old codebase and we're stuck with .Net Framework 3.5 so limited to C# 6 or something like that.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

九八野马 2025-01-23 02:29:00

C# 10 引入了 [CallerArgumentExpression] (docs),一种传递调用者源代码的字符串表示形式的方法。所以你可以编写一个辅助方法;

public void Log<T>(T value, [CallerArgumentExpression("value")] string name=null)
  => Log($"{name} = {value}");

但您也可以将其与另一个新功能 [InterpolatedStringHandler] (docs) 记录内插字符串内任何变量的名称。

[InterpolatedStringHandler]
public ref struct DebugLogHandler
{
    private readonly StringBuilder sb;
    public DebugLogHandler(int literalLen, int formattedCount)
    {
        sb = new StringBuilder(literalLen);
    }
    public void AppendLiteral(string s) => sb.Append(s);
    public void AppendFormatted<T>(T value, [CallerArgumentExpression("value")] string name=null)
    {
        sb.Append(name);
        sb.Append("=");
        sb.Append(value?.ToString());
    }
    public string BuildMessage() => sb.ToString();
}

public static void Log(string message) { ...TODO... }
public static void Log(DebugLogHandler builder)
    => Log(builder.BuildMessage());

var variableName = "value";
Log($"Something {variableName}");

C# 10 introduced [CallerArgumentExpression] (docs), a way to pass a string representation of the callers source code. So you could write a helper method;

public void Log<T>(T value, [CallerArgumentExpression("value")] string name=null)
  => Log(
quot;{name} = {value}");

But you could also combine this with another new feature, [InterpolatedStringHandler] (docs) to log the name of any variable inside an interpolated string.

[InterpolatedStringHandler]
public ref struct DebugLogHandler
{
    private readonly StringBuilder sb;
    public DebugLogHandler(int literalLen, int formattedCount)
    {
        sb = new StringBuilder(literalLen);
    }
    public void AppendLiteral(string s) => sb.Append(s);
    public void AppendFormatted<T>(T value, [CallerArgumentExpression("value")] string name=null)
    {
        sb.Append(name);
        sb.Append("=");
        sb.Append(value?.ToString());
    }
    public string BuildMessage() => sb.ToString();
}

public static void Log(string message) { ...TODO... }
public static void Log(DebugLogHandler builder)
    => Log(builder.BuildMessage());

var variableName = "value";
Log(
quot;Something {variableName}");
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文