我正在尝试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);
但是,当将CONDITIONAL
预处理程序指令添加到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??
实际返回值为"using MyUsing1;\r\n#if CONDITIONAL\r\nusing MyUsing2;"
。 #if CONDITIONAL
部分被保留,#endif
被删除。
这是一个错误,还是我做错了什么?
答案 0 :(得分:1)
在试图理解这种行为时,我添加了另一个要考虑的测试用例:
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
)中的字符“更大”范围,其中包括#if
指令之类的内容。
深入研究,文档将之前的代码(如preproc指令)称为“前导琐事”,并将其作为子节点附加到using节点。
有趣的是,如果仅通过using指令之一传递.AddUsings()
,则似乎省略了前导琐事。但是如果您给它一个包含多个UsingDirectiveSyntax
的数组,那么除第一个数组外,每个数组都包含前导琐事。 (这可能不完全正确;我仅从黑盒观测中进行工作。)
我不会假装理解这种行为的原因。结果是,很多看起来不错的代码(如您的示例)将产生令人不安的输出。 (如果您传递new[] {usings[0], usings[2], usings[1]}
,则会得到看起来更糟的输出,其中#endif
位于#if
之前。但是...您知道...我想您为什么要这样做? )
因此,如果您想使用这些工具来生成要反馈到完整构建管道中的源代码,则可以将其视为错误(或者至少可以将其很容易成为错误源)。如果有预期的用法可以使您摆脱这些问题,那么我找不到直接的文档。在这种情况下,您可以从usings
中删除琐事,然后再将其添加到输出中;但是在其他情况下,我认为这可能会丢失您想要保留的内容。