【问题标题】:How to use Roslyn generate an array of string literals that are each wrapped in an #if?如何使用 Roslyn 生成一个字符串数组,每个字符串都包含在 #if 中?
【发布时间】:2020-10-28 05:48:49
【问题描述】:

用例

在运行时了解哪些#define 符号用于构建C# 应用程序会很有帮助。由于实际上似乎不可能做到这一点,因此我改为使用 Roslyn 生成一个巨大的string[],每个#define 我可能有兴趣列出。每个#define 都应作为string 文字存储在此数组中,仅当关联符号为#defined 时才包含它本身,如下所示:

// 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 文字一样?

【问题讨论】:

  • 您要解决的问题是什么?为什么和罗斯林在一起?为什么不用 t4 脚本?
  • 如何使用List&lt;string&gt;,并为每个符号在#if-#endif 之间生成一个list.Add("literal");
  • @LegacyCode 所有这些代码都存在于由 Roslyn 提供支持的现有代码生成器的插件中。
  • @PeterB 除非我对您的建议有什么不明白的地方,否则这就是我正在做的事情(但使用 LINQ)。
  • 我的意思是这样的:private static readonly string[] _symbols = GetSymbols(); 后跟一个方法 private static string[] GetSymbols() { ... },其方法体创建一个 List&lt;string&gt;,然后是 Roslyn 生成的 #if ... list.Add("literal"); #endif 块,最终返回 @ 987654342@.

标签: c# code-generation roslyn directive


【解决方案1】:

Roslyn Quoter的帮助下,我想通了! Roslyn Quoter 是一个工具,可让您输入 C# sn-p 并查看生成它所需的 API 调用。

提供 Roslyn Quoter 我的原始代码示例返回的 sn-p 太长而无法逐字发布。但是,它有助于我编写更简单、更简洁的东西。

TLDR:要解决我的确切问题,您需要将所有内容作为SyntaxTrivia 节点的序列提供,该节点引导一个右大括号令牌(})。


这里有更多细节。我为每个#define 符号创建了三个SyntaxTrivia 节点:

  1. #if 指令
  2. string 文字,作为一段禁用文本(这很好,因为禁用文本本身很简单)
  3. #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
*/

之后,棘手的部分是数组初始化表达式。我是这样做的。

之后的棘手部分是将所有内容放入初始化表达式中,实际上它看起来像一个带有 很多 前导琐事的空数组初始化程序。我是这样做的:

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 包,里面装满了它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-29
    • 1970-01-01
    相关资源
    最近更新 更多