简体   繁体   English

使用Roslyn编译语法树

[英]Compiling a Syntax Tree using Roslyn

I'm trying to use Roslyn to generate and compile a runtime library of simple objects containing get/set properties. 我正在尝试使用Roslyn生成和编译包含get / set属性的简单对象的运行时库。

However, for some reason, compiling the assembly fails with error of adding the Linq namespace (error CS0246: The type or namespace name 'System.Linq' could not be found (are you missing a using directive or an assembly reference?)}). 但是,由于某种原因,编译程序集失败,添加Linq名称空间时出错(错误CS0246:找不到类型或名称空间名称'System.Linq'(您是否缺少using指令或程序集引用?)}) 。

I've tried manipulating the generated tree in a number of ways and compiling each, but still compilation fails. 我曾尝试以多种方式操作生成的树并编译每个,但仍然编译失败。

The only way in which compilation succeeds is if the tree is parsed to a string, then parsed back to a syntax tree and then compiled. 编译成功的唯一方法是将树解析为字符串,然后解析回语法树然后编译。

The code below does the following: 以下代码执行以下操作:

  1. Build a simple syntax tree containing compilation unit, usings, namespace, class and property. 构建一个包含编译单元,usings,namespace,class和property的简单语法树。
  2. Try to compile the tree (fails) 尝试编译树(失败)
  3. Generate new Syntax Tree with C#6 option and compile (fails) 使用C#6选项生成新的语法树并编译(失败)
  4. Format Syntax Tree and compile (fails) 格式化语法树并编译(失败)
  5. Serialize tree to string, then use SyntaxFactory.ParseSyntaxTree and compile the generated tree (success) 将树序列化为字符串,然后使用SyntaxFactory.ParseSyntaxTree并编译生成的树(成功)

The code: 编码:

    private static readonly CSharpCompilationOptions DefaultCompilationOptions =
        new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                .WithOverflowChecks(true)
                .WithPlatform(Platform.X86)
                .WithOptimizationLevel(OptimizationLevel.Release)
                .WithUsings(DefaultNamespaces);
    private static readonly IEnumerable<string> DefaultNamespaces =
        new[]
        {
                    "System",
                    "System.IO",
                    "System.Net",
                    "System.Linq",
                    "System.Text",
                    "System.Text.RegularExpressions"
        };

    private static readonly IEnumerable<MetadataReference> DefaultReferences =
        new[]
        {
                    MetadataReference.CreateFromFile(typeof (object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (System.Linq.Enumerable).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (System.GenericUriParser).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.Location)
        };

    static void Main(string[] args)
    {
        MakeAssembly();
        Console.ReadLine();
    }

    private static void MakeAssembly()
    {
        //Compilation Unit and Usings
        CompilationUnitSyntax cu = SyntaxFactory.CompilationUnit()
            .AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")),
            SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(System.Linq.Enumerable).Namespace)))
        ;

        // NameSpace
        NamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName("Roslyn"));

        // Class
        ClassDeclarationSyntax classNode = SyntaxFactory.ClassDeclaration("MyClass")
                        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                    ;

        // Property
        classNode= classNode.AddMembers(
                                SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("Int32"), "MyProperty")
                                        .AddAccessorListAccessors(
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))).
                                        AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
        ns = ns.AddMembers(classNode);
        cu = cu.AddMembers(ns);

        // Try To Compile Syntax Tree root
        var root = cu.SyntaxTree.GetRoot();
        var st = root.SyntaxTree;
        var assembly = CompileAndLoad(st);

        if (assembly != null)
        {
            Console.WriteLine("Success compile syntax tree root");
            return;
        }
        else
            Console.WriteLine("failed to compile syntax tree root");

        // Try to compile new syntax tree
        var stNew = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
        assembly = CompileAndLoad(stNew);
        if (assembly != null)
        {
            Console.WriteLine("Success compile new syntax tree");
            return;
        }
        else
            Console.WriteLine("failed to compile new syntax tree");

        // Try to format node
        AdhocWorkspace cw = new AdhocWorkspace();
        OptionSet options = cw.Options;
        options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, false);
        options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, false);

        SyntaxNode formattedNode = Formatter.Format(cu, cw, options);
        var stFormat = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
        assembly = CompileAndLoad(stFormat);
        if (assembly != null)
        {
            Console.WriteLine("Success compile formatted syntax tree");
            return;
        }
        else
            Console.WriteLine("failed to compile formatted syntax tree");


        // Try to serialize and parse
        StringBuilder sb = new StringBuilder();
        using (StringWriter writer = new StringWriter(sb))
        {
            formattedNode.WriteTo(writer);
        }
        var treeAsString = sb.ToString();
        var stParsed = SyntaxFactory.ParseSyntaxTree(treeAsString);
        assembly = CompileAndLoad(stParsed);
        if (assembly != null)
        {
            Console.WriteLine("Success compile parsed syntax tree");
            return;
        }   
        else
            Console.WriteLine("failed to compile formatted syntax tree");

    }

    private static Assembly CompileAndLoad(SyntaxTree st)
    {
        var compilation
            = CSharpCompilation.Create("TestRoslyn.dll", new SyntaxTree[] { st }, null, DefaultCompilationOptions);
        compilation = compilation.WithReferences(DefaultReferences);
        using (var stream = new MemoryStream())
        {
            EmitResult result = compilation.Emit(stream);
            if (result.Success)
            {
                var assembly = Assembly.Load(stream.GetBuffer());
                return assembly;
            }
            return null;
        }
    }

I fell into this trap with Roslyn also. 罗斯林也陷入了这个陷阱。 The using directive is not just expressed as a string each part of the qualified name is a syntax node. using指令不仅表示为字符串,限定名称的每个部分都是语法节点。 You need to create your node like this 您需要像这样创建节点

var qualifiedName= SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"),    
                                                 SyntaxFactory.IdentifierName("Linq"));     
var usingDirective = SyntaxFactory.UsingDirective(qualifedName);

I wrote a helper method to convert a string to the correct syntax node. 我编写了一个帮助方法来将字符串转换为正确的语法节点。

private UsingDirectiveSyntax CreateUsingDirective(string usingName)
{
    NameSyntax qualifiedName = null;

    foreach (var identifier in usingName.Split('.'))
    {
        var name = SyntaxFactory.IdentifierName(identifier);

        if (qualifiedName != null)
        {
            qualifiedName = SyntaxFactory.QualifiedName(qualifiedName, name);
        }
        else
        {
            qualifiedName = name;
        }
    }

    return SyntaxFactory.UsingDirective(qualifiedName);
}

You can use SyntaxFactory.ParseName which will handle parsing the string and then building the qualified name syntax node for your using directives : 您可以使用SyntaxFactory.ParseName来处理解析字符串,然后为using指令构建限定名称语法节点:

var qualifiedName = SyntaxFactory.ParseName("System.Linq");
var usingDirective = SyntaxFactory.UsingDirective(qualifiedName);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM