Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
安裝 Roslyn SDK (Preview)
在前一篇文章中我們介紹了在既有的 Visual Studio 2013(或是已經內建整合支援的 Visual Studio "14")裡安裝 Roslyn End User (Preview) 的 Visual Studio 插件,讓一些分析程式碼的工作交給 Roslyn 來處理。而在這篇文章要介紹的是,如何使用 Roslyn 所提供的 APIs 來做到這些程式碼分析的工作。要使用這些 APIs,除了可以下載安裝 Roslyn SDK (Preview) 的 Visual Studio 插件,就可直接建立含有 Roslyn 相關套件的專案範本:
也可以在專案中,透過 NuGet 套件管理系統來安裝 Roslyn NuGet 套件:
Install-Package Microsoft.CodeAnalysis -Pre
一樣可以安裝相關套件參考,並在程式中使用這些功能:
Syntax API
編譯器要對程式碼進行編譯,當然要先確定使用者寫的程式的語法(syntax),除了判斷語法是否正確之外,也要瞭解它的結構——哪些是變數、保留字、字串或數值等等。在 Roslyn 中提供的 Syntax API 就提供了解析(parse)語法、建立語法樹狀結構(syntax tree)以及一些相關的功能。一個典型的範例像是這樣:
SyntaxTree tree = CSharpSyntaxTree.ParseText(
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}");
var root = (CompilationUnitSyntax)tree.GetRoot();
您可以把一段完整的 C# (或 VB.net) 的程式碼餵進 CSharpSyntaxTree
的 ParseText
方法中,如此一來便會建立出一個語法樹狀結構,接著就可以走訪這棵樹來分析語法。例如,要拿到 Main 函式的引數列,就要像這樣來走訪語法樹,argsParameter
就是引數列的第一個引數資料。
var firstMember = root.Members[0];
var helloWorldDeclaration = (NamespaceDeclarationSyntax)firstMember;
var programDeclaration = (ClassDeclarationSyntax)helloWorldDeclaration.Members[0];
var mainDeclaration = (MethodDeclarationSyntax)programDeclaration.Members[0];
var argsParameter = mainDeclaration.ParameterList.Parameters[0];
總之 Syntax API 提供的 API 主要就是用來解析語法、並且建立語法樹狀結構,有了這棟樹,要怎麼搜尋或分析(相較於還要寫個 parser 而言...)就容易多了。
Compilation Class
Syntax API 只能建立語法樹,但光有這樣的樹狀結構還是無法瞭解這段程式碼到底在做什麼,每一個 syntax node 到底代表的是什麼東西,這時候就能使用 Compilation 物件類別來把語法樹編譯一下,這下你就可以得到這個程式碼的所有符號(symbol),所謂的符號指的就是像資料型態(type)、命名空間(namespace)還有變數的名稱及其代表的表示式等等,而把符號及表示式對應起來的動作就稱為繫結(Binding)。
而下面這段程式碼就是把上述 Syntax API 的範例做編譯,並且找出第一個引入的命名空間符號 systemSymbol
,比起只用 Syntax API 建立的樹狀結構,雖然也可以在語法樹上拿到 syntax node,但 syntax node 是無法知道它是一個命名空間,更別提去分析這個命名空間的成員結構等等。
var compilation = CSharpCompilation.Create("HelloWorld")
.AddReferences(new MetadataFileReference(typeof(object).Assembly.Location))
.AddSyntaxTrees(tree);
var model = compilation.GetSemanticModel(tree);
var nameInfo = model.GetSymbolInfo(root.Usings[0].Name);
var systemSymbol = (INamespaceSymbol)nameInfo.Symbol;
而在 IDE 中要做語法提示(IntelliSense)這個功能,就可以用符號的資訊來處理。像下面這段程式碼就是取得 "Hello, world"
這個符號後,從它的資料型態(string)來列出可以呼叫的方法,這就是一個 IntelliSense 的雛型了:
var helloWorldString = root.DescendantNodes()
.OfType()
.First();
var literalInfo = model.GetTypeInfo(helloWorldString);
var stringTypeSymbol = (INamedTypeSymbol)literalInfo.Type;
var methods = (from method in stringTypeSymbol.GetMembers().OfType()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility == Accessibility.Public
select method.Name).Distinct();
foreach (var name in methods)
{
Console.WriteLine(name);
}
它會印出所有字串資料可以呼叫的方法。
接下來
這篇文章介紹 Syntax API
以及 Compilation
物件類別,主要都還是在分析程式碼,在下一篇文章中將會介紹如何運用這些 APIs 來修改或者說重構程式碼。
參考資料
- Getting Started - Syntax Analysis (CSharp).pdf or Word docx
- Getting Started - Semantic Analysis (CSharp).pdf or Word docx
原始文章發佈於「開發者之魂」部落格