23

(我要做的是通过从 vs 生成的设置文件生成接口和包装类来解决Application.Settings/MVVM问题。)

我想做的是:

  • 从文件中解析类声明
  • 仅基于类的(非静态)属性生成接口声明
  • 生成一个实现此接口的包装类,在构造函数中获取原始类的一个实例,并将所有属性“管道”到该实例。
  • 生成另一个直接实现接口的类。

我的问题有两个:

  • 我在吠叫错误的树吗?我最好为此使用 Code-Dom、T4、Regex(!) 还是其中的一部分?(我不介意额外的工作,因为这主要是一种学习体验。)
  • 如果罗斯林是要走的路,我应该看哪一点?我有点天真地希望有某种方法可以走树并吐出我想要的部分,但是我很难弄清楚是否/如何使用 SyntaxRewriter 来做到这一点,或者是否使用流利的结构,多次查询源代码以获取我需要的位。

如果您想对 MVVM 方面发表评论,您可以,但这不是问题的主旨 :)

4

4 回答 4

13

如果您的要求是解析 C# 源代码,那么我认为 Roslyn 是一个不错的选择。如果你打算在这部分使用它,我认为将它用于代码生成也是有意义的。

使用 Roslyn 生成代码可能非常冗长(尤其是与 CodeDom 相比时),但我认为这对您来说不会是一个大问题。

我认为SyntaxRewriter最适合对代码进行本地化更改。但是你问的是解析整个类并基于它生成类型,我认为直接查询语法树效果最好。

例如,为类中的所有属性生成只读接口的最简单示例可能如下所示:

var originalClass =
    compilationUnit.DescendantNodes().OfType<ClassDeclarationSyntax>().Single();
string originalClassName = originalClass.Identifier.ValueText;
var properties =
    originalClass.DescendantNodes().OfType<PropertyDeclarationSyntax>();

var generatedInterface =
    SyntaxFactory.InterfaceDeclaration('I' + originalClassName)
          .AddMembers(
              properties.Select(
                  p =>
                  SyntaxFactory.PropertyDeclaration(p.Type, p.Identifier)
                        .AddAccessorListAccessors(
                            SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                                  .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))
                        .ToArray());
于 2013-05-02T14:48:23.223 回答
6

关于代码生成的问题,我的建议是实际使用内联代码片段(使用 解析CSharpSyntaxTree.ParseText)和手动生成的组合SyntaxNodes,但更倾向于前者。我过去也使用过 T4,但由于普遍缺乏集成和功能,我正在远离它们。

每个的优点/缺点:

罗斯林解析文本

  • 生成可以说更具可读性的代码生成器代码。
  • 允许“文本模板”方法,例如使用 C# 6 字符串插值。
  • 不那么冗长。
  • 保证有效的语法树。
  • 可以更高效
  • 更容易上手。
  • SyntaxNodes文本可能比大多数是程序性的更难阅读。

Roslyn 语法节点构建

  • 更适合转换现有的语法树 - 无需从头开始。
    • 但是现有的琐事会使这件事变得混乱/复杂。
  • 更冗长。可以说更难阅读和构建。
    • 语法树通常比你想象的更复杂
  • SyntaxFactoryAPI 提供有关有效语法的指导。
  • Roslyn Quoter帮助您将文本代码转换为工厂代码。
  • 语法树不一定有效。
  • 代码编写后可能更健壮。

T4 模板

  • 如果要生成的大部分代码都是样板代码,那就太好了。
  • 没有适当的 CI 支持。
  • 没有第 3 方扩展,就没有语法高亮或智能感知。
  • 输入和输出文件之间的一对一映射。
    • 如果您正在进行更复杂的生成,例如基于单个输入的整个类层次结构,则不理想。
  • 仍然可能希望使用 Roslyn 来“反映”输入类型,否则您将遇到 System.Reflection 和文件锁定等问题。
  • 不易被发现的 API。T4 包含、参数等可能会令人困惑。

Roslyn 代码生成技巧

  • 如果您只是解析代码片段,例如方法语句,那么您将需要使用CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)来获取正确的语法节点。
  • 如果您正在为方法体解析整个代码块,那么您需要将其解析为 a GlobalStatementSyntax,然后将Statement属性作为 a访问BlockSyntax
  • 使用辅助方法解析 single SyntaxNodes

        private static TSyntax ParseText<TSyntax>(string code, bool asScript = false)
        {
            var options = asScript
                ? CSharpParseOptions.Default.WithKind(SourceCodeKind.Script)
                : CSharpParseOptions.Default;
    
            var syntaxNodes =
                CSharpSyntaxTree.ParseText(code, options)
                    .GetRoot()
                    .ChildNodes();
    
            return syntaxNodes.OfType<TSyntax>().First();
        }
    
  • 手动构建时SyntaxNodes,您通常需要进行最终调用以SyntaxTree.NormalizeWhitespace(elasticTrivia: true)使代码“可往返”。
  • 通常,您将希望使用SyntaxNode.ToFullString()来获取包括琐事在内的实际代码文本。
  • SyntaxTree.WithFilePath()当您写出代码时,用作存储最终文件名的便利位置。
  • 如果您的目标是输出源文件,那么最终结果就是以 valid CompilationUnitSyntaxs.
  • 不要忘记使用Formatter.Format作为最后步骤之一的漂亮打印。
于 2016-05-23T02:20:04.230 回答
5

我认为 Roslyn 是解决这个问题的好方法。至于我将使用 Roslyn 的哪一部分 - 我可能会使用SyntaxWalker原始类,然后使用 Fluent API 为SyntaxNodes您想要生成的新类型构建新的。您可以在生成的代码中重用原始树的某些部分(例如,参数列表等)。

这可能看起来像的一个简单示例是:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Services;
using Roslyn.Services.CSharp;

    class Program
    {
        static void Main(string[] args)
        {
            var syntaxTree = SyntaxTree.ParseText(@"
class C
{
    internal void M(string s, int i)
    {
    }
}");


        }
    }


class Walker : SyntaxWalker
{
    private InterfaceDeclarationSyntax @interface = Syntax.InterfaceDeclaration("ISettings");

    private ClassDeclarationSyntax wrapperClass = Syntax.ClassDeclaration("SettingsWrapper")
        .WithBaseList(Syntax.BaseList(
            Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));

    private ClassDeclarationSyntax @class = Syntax.ClassDeclaration("SettingsClass")
        .WithBaseList(Syntax.BaseList(
            Syntax.SeparatedList<TypeSyntax>(Syntax.ParseTypeName("ISettings"))));

    public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
    {
        var parameters = node.ParameterList.Parameters.ToArray();
        var typeParameters = node.TypeParameterList.Parameters.ToArray();
        @interface = @interface.AddMembers(
            Syntax.MethodDeclaration(node.ReturnType, node.Identifier.ToString())
                .AddParameterListParameters(parameters)
                .AddTypeParameterListParameters(typeParameters));

        // More code to add members to the classes too.
    }
}
于 2013-05-02T14:48:45.757 回答
5

我正在做一些非常相似的事情,并且我也在使用 Roslyn 来解析现有的 C# 代码。但是,我使用T4 模板来生成新代码。T4 模板是为文本生成而设计的,并提供了一个非常好的抽象,因此您可以实际指定看起来像代码的东西,而不是这个疯狂的对象树。

于 2013-08-15T17:36:20.977 回答