首页 > 解决方案 > 如何返回 C# 方法调用的语法上下文

问题描述

如果你写类似

this.MyMethod(this.MyMethod(myParam)).Should().BeNull();

使用FluentAssertions库,当断言失败时抛出异常,并带有消息

Expected this.MyMethod(this.MyMethod(myParam)) to be <null>, but found "foo".

它如何设法获取方法调用的语法上下文以产生如此有意义的异常消息?显然使用了某种反射,并且可能使用了一些堆栈跟踪检查(实际上,它只在调试模式下以及当表达式写在一行中时才能正常工作),但究竟如何呢?

更具体地说,如何编写带有签名的方法

public static string GetSyntacticContext(object parameter);

满足(在调试模式下)以下?

GetSyntacticContext("foo")
    .Should().Be("\"foo\"");

GetSyntacticContext(myParam + 1)
    .Should().Be("myParam + 1");

GetSyntacticContext(this.MyMethod(this.MyMethod(myParam)))
    .Should().Be("this.MyMethod(this.MyMethod(myParam))");

标签: c#reflection

解决方案


您提到它仅在调试模式下有效,并且仅当表达式写在一行中时才有效。实际上,调试模式下的异常堆栈跟踪包含源文件的完整路径以及通向异常发生位置的每个堆栈帧的行号。

因此,库可以从文件中读取该行并对其进行检查。它可能会对该行进行完整的表达式解析,但他们更有可能选择使用正则表达式的快速而肮脏的路线。在您引用的所有示例中,它真正需要做的就是删除.Should()它之后的所有内容。

在您的位置上,我会稍微修改源文件(例如,改变间距和/或在行中间添加一些 /* 注释 */)以查看异常消息是否保留了这些。如果是这样,我们就知道它是从源文件中读取的。如果没有,我敢打赌它仍然从源文件中读取,但对相关行执行语法分析并重建它。

您假设的GetSyntacticContext方法可能会做类似的事情:获取当前堆栈帧及其文件名/行号信息,并从文件中读取该行。像这样的东西:

public static string GetSyntacticContext(object parameter)
{
    var st = new StackTrace(fNeedFileInfo: true);
    var frame = st.GetFrame(1);
    return File.ReadLines(frame.GetFileName()).Skip(frame.GetFileLineNumber() - 1).FirstOrDefault();
}

请注意,此示例未使用该parameter参数。在这个例子中,我没有尝试检测调用GetSyntacticContext并提取方法调用语法中的代码;它只返回整行代码、缩进和所有内容。

顺便说一句,该StackFrame类型也有一个GetMethod()方法,因此您可以获得对包含相关调用的方法的运行时引用GetSyntacticContext。不知道你可以用它做什么,但你可以获得它的 IL 代码,和/或分析伴随 EXE 文件的 PDB 文件的内容。不过,这并不会真正为您提供实际的 C# 源代码;PDB 文件最多包含您已经拥有的相同文件名/行号信息,并且它仍然不允许您区分在GetSyntacticContext同一语句中发生的多个调用。


推荐阅读