探索Roslyn CTP:为.NET开发者带来的新机遇

最近发布的Roslyn CTP为C#和VB.NET程序员带来了许多激动人心的可能性。Roslyn在.NET编译器服务的基础上提供了语言服务和API,这将使.NET开发者能够做更多的事情——包括将C#和VB.NET作为脚本语言使用,将编译器作为服务集成到自己的应用程序中进行代码相关任务,开发更好的语言和IDE扩展等。

要下载Roslyn CTP,请访问MSDN的Roslyn CTP页面及相关资源。此外,Roslyn为开发者提供了围绕Visual Studio IDE编写代码分析和操作工具的可能性,并提供了API,使能够快速开发Visual Studio增强功能。Roslyn API将与C#和VB的语法、语义绑定、代码发射等保持完全一致——很快将看到围绕C#和VB.NET的大量语言弯曲,可能还有新的DSL和许多元编程思想。

Roslyn主要有四个API层:

  1. 脚本API:为C#VB.NET提供运行时执行上下文。现在可以在自己的应用程序中将C#/VB.NET用作脚本语言。
  2. 编译器API:用于访问代码的语法和语义模型。
  3. 工作区API:提供对象模型,用于在解决方案中的项目间聚合代码模型。主要用于IDE(如Visual Studio)中的代码分析和重构,尽管API不依赖于Visual Studio。
  4. 服务API:在Visual Studio SDK(VSSDK)之上提供一层,用于Intellisense、代码格式化等功能。

在本文中,将一窥脚本API和编译器API。脚本API允许在自己的应用程序中使用C#和VB.NET作为脚本语言。Roslyn.Scripting.*命名空间提供了实现自己的脚本会话的类型,使用C#和VB.NET。可以使用Session.Create(..)方法创建一个新的脚本会话。Session.Create方法还可以接受一个Host对象,Host对象的方法将直接在运行时上下文中可用。

要执行一些代码,可以创建一个ScriptEngine实例,提供所需的程序集引用和命名空间导入信息,并调用Scripting Engine的Execute方法。为了演示这是如何工作的,让创建一个简单的ScriptingHost,它包装了一个脚本会话。

为了演示,还将创建一个ScriptedDog类,这个示例的目的是创建狗并通过脚本环境训练它们。安装Roslyn后,创建一个新的控制台应用程序,以下是演示简单脚本环境的代码:

using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using Roslyn.Compilers; using Roslyn.Compilers.CSharp; using Roslyn.Scripting; using Roslyn.Scripting.CSharp; namespace ScriptingRoslyn { public class OurDog { private string _name = string.Empty; public OurDog(string name) { _name = name; } public string Name { get { return _name; } } public void Bite(OurDog other) { Console.WriteLine("{0} is biting the tail of {1}", _name, other.Name); } public void Walk() { Console.WriteLine("{0} is Walking", _name); } public void Eat() { Console.WriteLine("{0} is Eating", _name); } } public class ScriptingHost { private ScriptEngine engine; private Session session; public OurDog CreateDog(string name) { return new OurDog(name); } public ScriptingHost() { session = Session.Create(this); engine = new ScriptEngine(new Assembly[] { typeof(Console).Assembly, typeof(ScriptingHost).Assembly, typeof(IEnumerable<>).Assembly, typeof(IQueryable).Assembly }, new string[] { "System", "System.Linq", "System.Collections", "System.Collections.Generic" }); } public object Execute(string code) { return engine.Execute(code, session); } public T Execute(string code) { return engine.Execute(code, session); } } class Program { static void Main(string[] args) { var host = new ScriptingHost(); Console.WriteLine("Hello Dog Trainer!! Type your code.\n\n"); string codeLine = string.Empty; Console.Write(">"); while ((codeLine = Console.ReadLine()) != "Exit();") { try { var res = host.Execute(codeLine); if (res != null) Console.WriteLine("= " + res.ToString()); } catch (Exception e) { Console.WriteLine("!! " + e.Message); } Console.Write(">"); } } } }

假设代码是自解释的。创建了一个Host类,其中包装了会话并使用该会话中的ScriptEngine执行代码。一个非常简单的REPL示例 - 所以,现在让去训练狗。

可以看到在Host类中调用CreateDog方法来创建狗,然后训练它们 - 尽管不确定否真的需要训练它们互相咬。无论如何,希望这个想法是清晰的。

编译器API具有用于访问代码的语法和语义模型的对象模型。使用编译器API,可以获取和操作语法树。常见的语法API可以在Roslyn.Compilers和Roslyn.Compilers.Common命名空间中找到,而特定于语言的语法API可以在Roslyn.Compilers.CSharp和Roslyn.Compilers.VisualBasic中找到。

“语法”是指语法结构,而“语义”指的是词汇符号排列在该结构中的含义。如果考虑英语,“Dogs Are Cats”在语法上是正确的,但在语义上是无意义的。

构建语法树和访问语义模型 Roslyn提供了构建语法树的API,还提供了进行语义分析的API。所以让写一些代码来解析一个方法并创建一个语法树。

using Roslyn.Compilers; using Roslyn.Compilers.CSharp; SyntaxTree tree = SyntaxTree.ParseCompilationUnit( @" class Bar { void Foo() { Console.WriteLine(""foo""); } } "); MethodDeclarationSyntax methodDecl = tree.Root .DescendentNodes() .OfType() .First().ChildNodes().OfType().First(); Compilation compilation = Compilation.Create( "SimpleMethod" ).AddSyntaxTrees(tree); var model = compilation.GetSemanticModel(tree); Symbol methodSymbol = model.GetDeclaredSymbol(methodDecl); Console.WriteLine(methodSymbol.Name);

在上面的示例中,可以轻松地看出如何将代码解析为SyntaxTree,获取与该语法树相关联的语义模型,然后查找语义模型中的信息。在这种情况下,获取与语法树中第一个类声明内的第一个方法声明相对应的方法符号。即DescendentNodes().OfType().First()的根节点给第一个类声明节点,ChildNodes().OfType().First()给该类声明内的第一个方法声明节点。

为了简洁起见,使用对象模型遍历语法树非常简单。

从语义模型中获得的符号可以用于包括代码分析在内的广泛场景。

关于语法树的更多信息 而不是将整个代码解析为语法树,还可以解析代码并为子语法节点 - 例如,可以将语句解析为StatementSyntax。

StatementSyntax statement = Syntax.ParseStatement( "for (int i = 0; i < 10; i++) { }" );

语法树以完全保真的方式持有整个源信息 - 这意味着它包含每一块源信息。此外,它们可以进行往返行程,从语法树或节点形成实际的源代码。语法树和节点是不可变的并且是线程安全的。然而,可以通过使用ReplaceNode函数完全替换当前语法树中的节点,从而创建一个用新节点替换旧节点的树。目前就是这样。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485