Roslyn入門(二)-C#語義分析

westwolf發表於2021-09-09

先決條件

簡介

今天,Visual Basic和C#編譯器是黑盒子:輸入文字然後輸出位元組,編譯管道的中間階段沒有透明性。使用.NET編譯器平臺(以前稱為“Roslyn”),工具和開發人員可以利用編譯器使用的完全相同的資料結構和演算法來分析和理解程式碼。

本篇文章,我們將探索SymbolBindingAPI。透過語法API來檢視解析器,語法樹,用於推理和構造它們的實用程式。

理解編譯和符號

這個語法API能讓你看程式的結構。但是,通常您需要有關程式語義或含義的更豐富資訊。雖然鬆散的程式碼片段可以單獨進行語法分析,但孤立的提出諸如“這個變數的型別是什麼”之類的問題並不是很有意義。型別名稱的含義可能取決於程式集引用,名稱空間匯入或其他程式碼檔案。這就是Compilation類的用武之地。

編譯類似於編譯器看到的單個專案,表示編譯Visual Basic或C#程式所需的所有內容,例如程式集引用,編譯器選項和要編譯的原始檔集。 透過此上下文,您可以推斷出程式碼的含義。 編譯允許您查詢符號 - 名稱和其他表示式引用的型別,名稱空間,成員和變數等實體。 將名稱和表示式與符號(Symbols)相關聯的過程稱為Binding。

與SyntaxTree一樣,Compilation是一個具有特定語言派生類的抽象類。建立Compilation例項時,必須在CSharpCompilation(或VisualBasicCompilation)類上呼叫工廠方法。

演示-建立編譯

  • 引入Nuget
  Microsoft.CodeAnalysis.CSharp
 
  Microsoft.CodeAnalysis.CSharp.Workspaces
  • 上節提到的演示Main程式碼
  class Program
   {
       static void Main(string[] args)
       {
           SyntaxTree tree = CSharpSyntaxTree.ParseText(
                       @"using System;
                       using System.Collections.Generic;
                       using System.Text;
                        
                       namespace HelloWorld
                       {
                           class Program
                           {
                               static void Main(string[] args)
                               {
                                   Console.WriteLine(""Hello, World!"");
                               }
                           }
                       }");

           var root = (CompilationUnitSyntax)tree.GetRoot();
        }
   }
           
  • 接下來,在Main方法的末尾建立CSharpCompilation物件
var compilation = CSharpCompilation.Create("HelloWorld")
                                               .AddReferences(
                                                    MetadataReference.CreateFromFile(
                                                        typeof(object).Assembly.Location))
                                               .AddSyntaxTrees(tree);
  • 設定斷點,啟動除錯,在compilation處檢視提示。

語義模型SemanticModel

一旦你有了編譯,你可以要求它為該編譯中包含的任何SyntaxTree提供SemanticModel。你可以查詢SemanticModel來回答諸如“這個位置的範圍是什麼名稱?”,“從這種方法可以獲得哪些成員?” ,“在這個文字塊中使用了哪些變數?”和“這個名字/表達是指什麼?”之類的問題。

示例-繫結名稱

此示例顯示如何為HelloWorld SyntaxTree獲取SemanticModel物件。獲得SemanticModel後,第一個using指令中的名稱繫結為System名稱空間的符號。

  • 將下段程式碼放到Main的末尾。

   var model = compilation.GetSemanticModel(tree);
   
   var nameInfo = model.GetSymbolInfo(root.Usings[0].Name);
   
   var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;
  

*追加以下程式碼,列舉System名稱空間的子名稱空間並將其名稱列印到控制檯:

 foreach (var ns in systemSymbol.GetNamespaceMembers())
            {
                Console.WriteLine(ns.Name);
            }
  • Debug進入除錯,檢視每個節點的值。Console輸出結果如下:
Buffers
Collections
ComponentModel
Configuration
Diagnostics
Globalization
IO
Numerics
Reflection
Resources
Runtime
Security
StubHelpers
Text
Threading

示例–繫結表示式

前面的示例顯示瞭如何繫結name去查詢Symbol。但是,在C#程式中還有其他不是Name的表示式可以繫結。此示例顯示繫結如何與其他表示式型別一起使用 - 在本例中為簡單的字串文字。

var helloWorldString = root.DescendantNodes()
                                       .OfType<LiteralExpressionSyntax>()
                                       .First();
                                       
var literalInfo = model.GetTypeInfo(helloWorldString);


var stringTypeSymbol = (INamedTypeSymbol)literalInfo.Type;
 
Console.Clear();
            
foreach (var name in (from method in stringTypeSymbol.GetMembers()
                                                      .OfType<IMethodSymbol>()
                       where method.ReturnType.Equals(stringTypeSymbol) &&
                             method.DeclaredAccessibility == 
                                        Accessibility.Public
                       select method.Name).Distinct())
{
    Console.WriteLine(name);
}
  • 執行Debug,檢視相關節點的值,Console輸出結果如下
Intern
IsInterned
Create
Copy
ToString
Normalize
Concat
Format
Insert
Join
PadLeft
PadRight
Remove
Replace
Substring
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
Trim
TrimStart
TrimEnd

總結

本篇文章演示了語義分析,透過兩個示例分別演示繫結Name查詢Symbol繫結表示式
我們可以獲得以下幾個知識點:

獲取語法樹的根節點:(CompilationUnitSyntax)tree.GetRoot()

用於建立CSharpCompilation物件:CSharpCompilation.Create(“HelloWorld”).AddReferences( MetadataReference.CreateFromFile( typeof(object).Assembly.Location))
.AddSyntaxTrees(tree)

用於獲取模型:compilation.GetSemanticModel(tree);

獲取Name的Symbol資訊: model.GetSymbolInfo(root.Usings[0].Name);

可獲取具體名稱空間名:(INamespaceSymbol)nameInfo.Symbol;

獲取當前名稱空間下所有成員:systemSymbol.GetNamespaceMembers()

獲取文字表示式:root.DescendantNodes().OfType()

獲取Type資訊 model.GetTypeInfo(helloWorldString)

獲取具體的Type型別 (INamedTypeSymbol)literalInfo.Type;

獲取返回值是string,公開型別的方法:
from method in stringTypeSymbol.GetMembers().OfType()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility ==
Accessibility.Public
select

原始碼

參考連結

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4662/viewspace-2816002/,如需轉載,請註明出處,否則將追究法律責任。

相關文章