Roslyn入門(二)-C#語義

先決條件

Visual Studio 2017git

.NET Compiler Platform SDKgithub

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

簡介

今天,Visual Basic和C#編譯器是黑盒子:輸入文本而後輸出字節,編譯管道的中間階段沒有透明性。使用.NET編譯器平臺(之前稱爲「Roslyn」),工具和開發人員能夠利用編譯器使用的徹底相同的數據結構和算法來分析和理解代碼。數據結構

本篇文章,咱們將探索Symbol和BindingAPI。經過語法API來查看解析器,語法樹,用於推理和構造它們的實用程序。數據結構和算法

理解編譯和符號

這個語法API能讓你看程序的結構。可是,一般您須要有關程序語義或含義的更豐富信息。雖然鬆散的代碼片斷能夠單獨進行語法分析,但孤立的提出諸如「這個變量的類型是什麼」之類的問題並非頗有意義。類型名稱的含義可能取決於程序集引用,命名空間導入或其餘代碼文件。這就是Compilation類的用武之地。工具

編譯相似於編譯器看到的單個項目,表示編譯Visual Basic或C#程序所需的全部內容,例如程序集引用,編譯器選項和要編譯的源文件集。 經過此上下文,您能夠推斷出代碼的含義。 編譯容許您查找符號 - 名稱和其餘表達式引用的類型,名稱空間,成員和變量等實體。 將名稱和表達式與符號(Symbols)相關聯的過程稱爲Binding。spa

與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來回答諸如「這個位置的範圍是什麼名稱?」,「從這種方法能夠得到哪些成員?」 ,「在這個文本塊中使用了哪些變量?」和「這個名字/表達是指什麼?」之類的問題。code

示例-綁定名稱

此示例顯示如何爲HelloWorld SyntaxTree獲取SemanticModel對象。得到SemanticModel後,第一個using指令中的名稱綁定爲System命名空間的符號。orm

  • 將下段代碼放到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<LiteralExpressionSyntax>()

獲取Type信息 model.GetTypeInfo(helloWorldString)

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

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

源碼

csharpfandemo

參考連接

Getting Started C# Semantic Analysis

相關文章
相關標籤/搜索