如何使用 Roslyn 生成一个字符串文字数组,每个字符串文字都包含在 #if 中?
How to use Roslyn generate an array of string literals that are each wrapped in an #if?
用例
在运行时了解哪些 #define
符号用于构建 C# 应用程序会很有帮助。由于实际上似乎不可能做到这一点,我改为使用 Roslyn 生成一个巨大的 string[]
,每个 #define
我可能对列表感兴趣。每个 #define
都应该作为 string
文字存储在这个数组中,只有当相关符号是 #define
d 时才会包含它本身,如下所示:
// This class is generated, do not change it manually.
public static class DefineSymbols
{
public static System.Collections.Generic.IReadOnlyList<string> Symbols => _symbols;
private static readonly string[] _symbols = new string[] {
#if DEBUG
"DEBUG",
#endif
#if UNITY
"UNITY",
#endif
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS",
#endif
// ...and lots more symbols...
};
}
问题
最简单的部分是生成一个庞大的 string
文字数组。困难的部分是正确放置逗号。使用此代码...
private SyntaxNode CreateSymbolsArray(SyntaxGenerator generator, string[] defines)
{
var stringType = generator.TypeExpression(SpecialType.System_String);
return generator.FieldDeclaration(
name: "_symbols",
type: generator.ArrayTypeExpression(stringType),
accessibility: Accessibility.Private,
modifiers: DeclarationModifiers.Static | DeclarationModifiers.ReadOnly,
initializer: generator.ArrayCreationExpression(
generator.TypeExpression(SpecialType.System_String),
defines.Select(d => CreateDefine(generator, d))
)
);
}
private SyntaxNode CreateDefine(SyntaxGenerator generator, string define)
{
var @if = SyntaxFactory.IfDirectiveTrivia(SyntaxFactory.ParseExpression(define), true, true, true);
var @endif = SyntaxFactory.EndIfDirectiveTrivia(true);
return generator.LiteralExpression(define)
.WithLeadingTrivia(SyntaxFactory.Trivia(@if))
.WithTrailingTrivia(SyntaxFactory.Trivia(@endif))
;
}
...在 #if
/#endif
对之外放置逗号,因此生成的代码将无法编译,除非定义了每个测试符号。
private static readonly string[] _symbols = new string[] {
#if DEBUG
"DEBUG"
#endif
,
#if UNITY
"UNITY"
#endif
,
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS"
#endif
// ...and lots more symbols...
};
所以,这是我的问题:我如何确保逗号包含在 #if
/#endif
对中,就像 string
文字一样?
在Roslyn Quoter的帮助下,我想通了! Roslyn Quoter 是一种工具,可让您输入 C# 片段并查看 API 生成它所需的调用。
向 Roslyn Quoter 提供我的原始代码示例 returns 一个太长的片段,无法 post 逐字记录。但是,它有助于我写一些更简单、更简洁的东西。
TLDR: 要解决我的确切问题,您需要提供所有内容作为 SyntaxTrivia
节点序列,这些节点导致右括号标记(}
).
这里有更多详细信息。我为每个 #define
符号创建了三个 SyntaxTrivia
节点:
#if
指令
string
文字,作为一段禁用文本(这很好,因为禁用文本本身很简单)
#endif
指令
这是它的样子:
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ...later, within the actual class...
private SyntaxTrivia[] CreateDefine(string define)
{
return new[]
{
Trivia(
IfDirectiveTrivia(
IdentifierName(define),
true,
false,
false
)
), // # if MY_DEFINE
DisabledText($" \"{define}\",\n"), // "MY_DEFINE",
Trivia(EndIfDirectiveTrivia(true)), // #endif
};
}
/* Output looks like this:
#if MY_DEFINE
"MY_DEFINE",
#endif
*/
之后,棘手的部分是数组初始化表达式。这是我的做法。
之后棘手的部分是将所有内容都放在一个初始化表达式中,实际上它看起来像一个带有 lot 前导琐事的空数组初始化器。我是这样做的:
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ...later, within the actual class...
private SyntaxNode CreateDefinesArrayInitializerExpression(string[] defines)
{
var stringType = PredefinedType(Token(SyntaxKind.StringKeyword)); // string
var arrayType = ArrayType(stringType)
.WithRankSpecifiers(
SingletonList(
ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(
OmittedArraySizeExpression()
)
) // []
)
)
; // string[]
return ArrayCreationExpression(arrayType) // new
.WithInitializer(
InitializerExpression(SyntaxKind.ArrayInitializerExpression) // string[] {
.WithCloseBraceToken(
Token(
TriviaList(defines.SelectMany(CreateDefine)), // <all the #defines>
SyntaxKind.CloseBraceToken, // }
TriviaList() // <nothing>
)
)
)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) // ;
;
}
/* Output looks like this:
new string[] {
#if DEBUG
"DEBUG"
#endif
,
#if UNITY
"UNITY"
#endif
,
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS"
#endif
};
*/
比较冗长,但是你可以写一些扩展方法来帮忙;我做了,但我省略了它们以简化复制粘贴。甚至可能有一个充满它们的 NuGet 包漂浮在某个地方。
用例
在运行时了解哪些 #define
符号用于构建 C# 应用程序会很有帮助。由于实际上似乎不可能做到这一点,我改为使用 Roslyn 生成一个巨大的 string[]
,每个 #define
我可能对列表感兴趣。每个 #define
都应该作为 string
文字存储在这个数组中,只有当相关符号是 #define
d 时才会包含它本身,如下所示:
// This class is generated, do not change it manually.
public static class DefineSymbols
{
public static System.Collections.Generic.IReadOnlyList<string> Symbols => _symbols;
private static readonly string[] _symbols = new string[] {
#if DEBUG
"DEBUG",
#endif
#if UNITY
"UNITY",
#endif
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS",
#endif
// ...and lots more symbols...
};
}
问题
最简单的部分是生成一个庞大的 string
文字数组。困难的部分是正确放置逗号。使用此代码...
private SyntaxNode CreateSymbolsArray(SyntaxGenerator generator, string[] defines)
{
var stringType = generator.TypeExpression(SpecialType.System_String);
return generator.FieldDeclaration(
name: "_symbols",
type: generator.ArrayTypeExpression(stringType),
accessibility: Accessibility.Private,
modifiers: DeclarationModifiers.Static | DeclarationModifiers.ReadOnly,
initializer: generator.ArrayCreationExpression(
generator.TypeExpression(SpecialType.System_String),
defines.Select(d => CreateDefine(generator, d))
)
);
}
private SyntaxNode CreateDefine(SyntaxGenerator generator, string define)
{
var @if = SyntaxFactory.IfDirectiveTrivia(SyntaxFactory.ParseExpression(define), true, true, true);
var @endif = SyntaxFactory.EndIfDirectiveTrivia(true);
return generator.LiteralExpression(define)
.WithLeadingTrivia(SyntaxFactory.Trivia(@if))
.WithTrailingTrivia(SyntaxFactory.Trivia(@endif))
;
}
...在 #if
/#endif
对之外放置逗号,因此生成的代码将无法编译,除非定义了每个测试符号。
private static readonly string[] _symbols = new string[] {
#if DEBUG
"DEBUG"
#endif
,
#if UNITY
"UNITY"
#endif
,
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS"
#endif
// ...and lots more symbols...
};
所以,这是我的问题:我如何确保逗号包含在 #if
/#endif
对中,就像 string
文字一样?
在Roslyn Quoter的帮助下,我想通了! Roslyn Quoter 是一种工具,可让您输入 C# 片段并查看 API 生成它所需的调用。
向 Roslyn Quoter 提供我的原始代码示例 returns 一个太长的片段,无法 post 逐字记录。但是,它有助于我写一些更简单、更简洁的东西。
TLDR: 要解决我的确切问题,您需要提供所有内容作为 SyntaxTrivia
节点序列,这些节点导致右括号标记(}
).
这里有更多详细信息。我为每个 #define
符号创建了三个 SyntaxTrivia
节点:
#if
指令string
文字,作为一段禁用文本(这很好,因为禁用文本本身很简单)#endif
指令
这是它的样子:
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ...later, within the actual class...
private SyntaxTrivia[] CreateDefine(string define)
{
return new[]
{
Trivia(
IfDirectiveTrivia(
IdentifierName(define),
true,
false,
false
)
), // # if MY_DEFINE
DisabledText($" \"{define}\",\n"), // "MY_DEFINE",
Trivia(EndIfDirectiveTrivia(true)), // #endif
};
}
/* Output looks like this:
#if MY_DEFINE
"MY_DEFINE",
#endif
*/
之后,棘手的部分是数组初始化表达式。这是我的做法。
之后棘手的部分是将所有内容都放在一个初始化表达式中,实际上它看起来像一个带有 lot 前导琐事的空数组初始化器。我是这样做的:
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ...later, within the actual class...
private SyntaxNode CreateDefinesArrayInitializerExpression(string[] defines)
{
var stringType = PredefinedType(Token(SyntaxKind.StringKeyword)); // string
var arrayType = ArrayType(stringType)
.WithRankSpecifiers(
SingletonList(
ArrayRankSpecifier(
SingletonSeparatedList<ExpressionSyntax>(
OmittedArraySizeExpression()
)
) // []
)
)
; // string[]
return ArrayCreationExpression(arrayType) // new
.WithInitializer(
InitializerExpression(SyntaxKind.ArrayInitializerExpression) // string[] {
.WithCloseBraceToken(
Token(
TriviaList(defines.SelectMany(CreateDefine)), // <all the #defines>
SyntaxKind.CloseBraceToken, // }
TriviaList() // <nothing>
)
)
)
.WithSemicolonToken(Token(SyntaxKind.SemicolonToken)) // ;
;
}
/* Output looks like this:
new string[] {
#if DEBUG
"DEBUG"
#endif
,
#if UNITY
"UNITY"
#endif
,
#if UNITY_ASSERTIONS
"UNITY_ASSERTIONS"
#endif
};
*/
比较冗长,但是你可以写一些扩展方法来帮忙;我做了,但我省略了它们以简化复制粘贴。甚至可能有一个充满它们的 NuGet 包漂浮在某个地方。