如何 return C# 方法调用的语法上下文

How to return the syntactic context of a C# method call

如果你这样写

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))");

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

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

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

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

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 的调用并提取方法调用语法中的代码;它只是 returns 整行代码,缩进等等。

顺便说一句,StackFrame 类型也有一个 GetMethod() 方法,因此您可以获得对包含对 GetSyntacticContext 的相关调用的方法的运行时引用。不确定你可以用它做什么,但你可以获得它的 IL 代码,and/or 分析 EXE 文件附带的 PDB 文件的内容。不过,这不会真正为您提供实际的 C# 源代码; PDB 文件充其量包含您已有的相同文件name/line 编号信息,并且它仍然不允许您区分在同一语句中发生的对GetSyntacticContext 的多次调用。