Roslyn入門(一)-C#語法分析


演示環境

Visual Studio 2017

.NET Compiler Platform SDK

簡介

今天,Visual Basic和C#編譯器是黑盒子:輸入文本然后輸出字節,編譯管道的中間階段沒有透明性。使用.NET編譯器平台(以前稱為“Roslyn”),工具和開發人員可以利用編譯器使用的完全相同的數據結構和算法來分析和理解代碼。 本篇文章,我們將會慢慢熟悉語法API,通過語法API來查看解析器,語法樹,用於推理和構造它們的實用程序。

理解語法樹

Trivia,Token和Node形成了一個完全代表Visual Basic或C#代碼片段中所有內容的樹

SyntaxTree

它的實例表示整個解析樹。SyntaxTree是一個抽象類,具有特定於語言的派生類。要解析特定語言的語法,您需要使用CSharpSyntaxTree(或VisualBasicSyntaxTree)類上的解析方法。

SyntaxNode

它的實例表示的語法結構如聲明,語句,子句和表達式。

SyntaxToken

它代表一個單獨的關鍵字,識別符,操作員或標點符號

SyntaxTrivia

它表示語法上無關緊要的信息,例如令牌之間的空白,預處理指令和注釋。

下圖示例:SyntaxNode: 藍色 | SyntaxToken: 綠色 | SyntaxTrivia: 紅色

語法解析樹

遍歷語法樹

  • 新建項目“CodeAnalysisDemo”
  • 引入Nuget
  Microsoft.CodeAnalysis.CSharp
 
  Microsoft.CodeAnalysis.CSharp.Workspaces
  • 命名空間:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
  • 准備要分析的代碼
using System;

namespace UsingCollectorCS
{
   class Program
   {
       static void Main(string[] args)
       {
           Console.WriteLine("Hello World");
       }
   }

   class Student
   {
       public string Name { get; set; }
   }
}
  • 核心代碼
       /// <summary>
       ///解析語法樹 /// </summary> /// <param name="code"></param> /// <returns></returns> public SyntaxNode GetRoot(string code) { var tree = CSharpSyntaxTree.ParseText(code); //SyntaxTree的根root var root = (CompilationUnitSyntax)tree.GetRoot(); //member var firstmember = root.Members[0]; //命名空間Namespace var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstmember; //類 class var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0]; //方法 Method var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0]; //參數 Parameter var argsParameter = mainDeclaration.ParameterList.Parameters[0]; //查詢方法,查詢方法名稱為Main的第一個參數。 var firstParameters = from methodDeclaration in root.DescendantNodes() .OfType<MethodDeclarationSyntax>() where methodDeclaration.Identifier.ValueText == "Main" select methodDeclaration.ParameterList.Parameters.First(); var argsParameter2 = firstParameters.Single(); return root; } 
  • 入口Main方法
var code = @"using System;

                       namespace UsingCollectorCS
                       {
                           class Program
                           {
                               static void Main(string[] args)
                               {
                                   Console.WriteLine(""Hello World"");
                               }
                           }

                           class Student
                           {
                               public string Name { get; set; }
                           }
                       }";

           var tree = new AnalysisDemo().GetRoot(code);
  • Debug調試

經過對比可知以下部分

利用CSharpSyntaxTree.ParseText(code)獲取語法樹SyntaxTree

利用(CompilationUnitSyntax)tree.GetRoot()獲取語法樹的跟節點

利用 (NamespaceDeclarationSyntax)root.Members[0]可獲取命名空間

利用 (ClassDeclarationSyntax)helloWorldDeclaration.Members[0]可獲取類

利用 (MethodDeclarationSyntax)programDeclaration.Members[0]可獲取方法

利用linq查詢,可從**root.DescendantNodes()**節點內查詢方法/參數等成員。

SyntaxWalkers

通常,您需要在語法樹中查找特定類型的所有節點,例如,文件中的每個屬性聲明。

通過擴展CSharpSyntaxWalker類並重寫VisitPropertyDeclaration方法,您可以在不事先知道其結構的情況下處理語法樹中的每個屬性聲明。

CSharpSyntaxWalker是一種特殊的SyntaxVisitor,它以遞歸方式訪問節點及其每個子節點。

我們先來演示CSharpSyntaxWalker的兩個虛virtual方法VisitUsingDirective 和VisitPropertyDeclaration

  • 核心代碼如下:
/// <summary>
    /// 收集器 /// </summary> public class UsingCollector : CSharpSyntaxWalker { public readonly Dictionary<string, List<string>> models = new Dictionary<string, List<string>>(); public readonly List<UsingDirectiveSyntax> Usings = new List<UsingDirectiveSyntax>(); public override void VisitUsingDirective(UsingDirectiveSyntax node) { if (node.Name.ToString() != "System" && !node.Name.ToString().StartsWith("System.")) { this.Usings.Add(node); } } public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) { var classnode = node.Parent as ClassDeclarationSyntax; if (!models.ContainsKey(classnode.Identifier.ValueText)) { models.Add(classnode.Identifier.ValueText, new List<string>()); } models[classnode.Identifier.ValueText].Add(node.Identifier.ValueText); } } /// <summary> /// 演示CSharpSyntaxWalker /// </summary> /// <param name="code"></param> /// <returns></returns> public UsingCollector GetCollector(string code) { var tree = CSharpSyntaxTree.ParseText(code); var root = (CompilationUnitSyntax)tree.GetRoot(); var collector = new UsingCollector(); collector.Visit(root); return collector; } 
  • Main調用入口:

            var code2 =
            @"using System;
                        using System.Collections.Generic;
                        using System.Linq;
                        using System.Text;
                        using Microsoft.CodeAnalysis;
                        using Microsoft.CodeAnalysis.CSharp;


            namespace TopLevel
                {
                    using Microsoft;
                    using System.ComponentModel;

                    namespace Child1
                    {
                        using Microsoft.Win32;
                        using System.Runtime.InteropServices;

                        class Foo {  
                            public string FChildA{get;set;}
                            public string FChildB{get;set;}
                        }
                    }

                    namespace Child2
                    {
                        using System.CodeDom;
                        using Microsoft.CSharp;

                        class Bar {
                             public string BChildA{get;set;}
                             public string BChildB{get;set;}
                        }
                    }
                }";

            var collector = new AnalysisDemo().GetCollector(code2);

            foreach (var directive in collector.Usings)
            {
                Console.WriteLine($"Name:{directive.Name}");
            }
            Console.WriteLine($"models:{JsonConvert.SerializeObject(collector.models)}");
  • 執行結果

我們可以得出結論

VisitUsingDirective 主要用於獲取Using命名空間

VisitPropertyDeclaration主要用於獲取屬性。

總結

本篇文章主要講了

  • 語法樹SyntaxTree,以及SyntaxNode,SyntaxToken,SyntaxTrivia。

  • 通過重寫CSharpSyntaxWalker的虛方法,可以實現自定義獲取。

  • 附上官方截取的部分流程圖

Roslyn編譯管道功能區

編譯步驟

API圖層

Roslyn由兩個主要的API層組成 - 編譯器API和工作區API。

源碼

CsharpFanDemo

參考鏈接

Roslyn-Overview

Getting Started C# Syntax Analysis

從零開始學習 dotnet 編譯過程和 Roslyn 源碼分析

手把手教你寫 Roslyn 修改編譯


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM