C# 使用预处理器指令解析和生成代码

C# 使用预处理器指令解析和生成代码,c#,roslyn,C#,Roslyn,我正在用roslyn做实验,解析并生成c代码。我试图弄清楚CSharpSyntaxTree.ParseText方法如何处理预处理器符号 这是我的测试方法。它将一些C代码作为字符串接收,提取using语句并返回一个新字符串,其中包含那些using语句,同时考虑预处理器指令 private static string Process(string input, string[] preprocessorSymbols) { var options = CSharpParseOptions.D

我正在用roslyn做实验,解析并生成c代码。我试图弄清楚CSharpSyntaxTree.ParseText方法如何处理预处理器符号

这是我的测试方法。它将一些C代码作为字符串接收,提取using语句并返回一个新字符串,其中包含那些using语句,同时考虑预处理器指令

private static string Process(string input, string[] preprocessorSymbols)
{
    var options = CSharpParseOptions.Default.WithPreprocessorSymbols(preprocessorSymbols);
    var syntaxTree = CSharpSyntaxTree.ParseText(input, options);
    var compilationUnit = (CompilationUnitSyntax)syntaxTree.GetRoot();
    var usings = compilationUnit.Usings.ToArray();
    var cs = SyntaxFactory.CompilationUnit()
            .AddUsings(usings)
            .NormalizeWhitespace();
    var result = cs.ToString();
    return result;
}
当使用以下输入输入此方法时,它将按预期工作:

var input = "using MyUsing1;\r\nusing MyUsing2;";
string result = Process(input, new[] { "" });
Assert.AreEqual("using MyUsing1;\r\nusing MyUsing2;", result);
当添加预处理器指令,但不将所述指令传递给解析器时,结果仍然是预期的条件语句,using语句被剥离:

var input =
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif";
string result = Process(input, new[] { "" });
Assert.AreEqual("using MyUsing1;", result);
然而,当将条件预处理器指令添加到CSharpParseOptions时,我得到了一个奇怪的结果

var input = 
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif";
string result = Process(input, new[] { "CONDITIONAL" });
Assert.AreEqual("using MyUsing1;\r\nusing MyUsing2;", result); // fails??
实际返回值使用MyUsing1\r\n如果有条件\r\n使用MyUsing2;。保留if条件部分,删除endif


这是一个bug,还是我做错了什么?

在试图理解这种行为时,我添加了另一个要考虑的测试用例:

var input =
    "using MyUsing1;\r\n" +
    "#if CONDITIONAL\r\n" +
    "using MyUsing2;\r\n" +
    "#endif" +
    "using MyUsing3;\r\n";
string result = Process(input, new[] { "CONDITIONAL" });
在这种情况下,if和endif都被保留

如果中断调试器并查看usings数组,则每个UsingDirectiveSyntax似乎都知道using语句Span的最小字符范围,以及原始流FullSpan中更大范围的字符,在本例中,原始流FullSpan包括If指令等

再深入一点,文档引用了前面的代码,比如preproc指令,作为主要的琐事,它作为子节点附加到using节点

有趣的是,如果您只传递using指令中的一个,那么它似乎忽略了主要的琐事;但是如果你给它一个多个usingdirectivesyntax的数组,那么除了第一个之外,每个数组都包含了主要的琐事。这可能并不完全正确;我只是从黑匣子观察工作

我不会假装理解这种行为的原因。结果是,许多看起来正常的代码位(如您的示例)将产生令人不安的输出。如果传入新[]{usings[0]、usings[2]、usings[1]},则会得到更糟糕的输出,其中endif在If之前。但是你知道的。。。我想你为什么要这么做


因此,如果您想使用这些工具生成源代码并反馈到完整的构建管道中,您可以将其视为一个bug,或者至少是一个很容易成为bug来源的奇怪行为。如果有一个预期的用法可以让您远离这一点,我找不到直接的文档。在这种情况下,您可以在将using添加到输出之前删除using中的琐事;但在其他情况下,我认为这可能会丢失您想要保留的内容。

您看过了吗,它可以显示脚本的语法树。谢谢您的帮助。同时,通过重读,结果证明指令是“琐事”,总是与下一行关联。对于这个用例来说很奇怪,但我想它是按照设计工作的。。。