使用Roslyn编译语法树

时间:2016-04-25 07:06:27

标签: c# syntax tree roslyn

我正在尝试使用Roslyn生成和编译包含get / set属性的简单对象的运行时库。

但是,由于某种原因,编译程序集失败,添加Linq名称空间时出错(错误CS0246:找不到类型或名称空间名称'System.Linq'(您是否缺少using指令或程序集引用? )})。

我已尝试以多种方式操作生成的树并编译每个,但仍然编译失败。

编译成功的唯一方法是将树解析为字符串,然后解析回语法树然后编译。

以下代码执行以下操作:

  1. 构建一个包含编译单元,usings,namespace,class和property的简单语法树。
  2. 尝试编译树(失败)
  3. 使用C#6选项生成新的语法树并编译(失败)
  4. 格式化语法树并编译(失败)
  5. 将树序列化为字符串,然后使用SyntaxFactory.ParseSyntaxTree并编译生成的树(成功)
  6. 代码:

        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;
            }
        }
    

2 个答案:

答案 0 :(得分:8)

我也和Roslyn陷入了这个陷阱。 using指令不仅表示为字符串,限定名称的每个部分都是语法节点。您需要像这样创建节点

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

我编写了一个帮助方法,将字符串转换为正确的语法节点。

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);
}

答案 1 :(得分:3)

您可以使用SyntaxFactory.ParseName处理解析字符串,然后为 using指令构建限定名称语法节点

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