Roslyn 是以 API 為驅動的下一代編譯器,集成在最新版的 Visual Studio 上。它開放 C# 和 Visual Basic 編譯器的 API,使得開發者可以借助編譯器進行解析代碼文件、動態為編程語言增加功能、擴展編譯器、自定義編譯器動作等操作。
將Roslyn編譯結果保存在流中,用程序集加載方法將流加載到當前程序集中,就可以在當前的程序集中調用了。
Roslyn支持兩種方式的動態編譯:
源代碼動態編譯就是對C#或VB.Net原代碼進行解析編譯,源代碼動態編譯實現簡單易於上手,但是編譯效率較低,適合小量的動態編譯工作和初期開發人員。
源代碼動態編譯示例:
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@" using System; namespace RoslynCompileSample { public class Writer { public void Write(string message) { Console.WriteLine(message); } } }"); string assemblyName = Path.GetRandomFileName(); MetadataReference[] references = new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) }; CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); }
這樣就成功編譯了一個動態程序集,這個程序集引用了當前運行時的程序集,在動態程序集中可以引用當前程序集的命名空間,通過下面的反射就可以調用這個動態程序集了;
Type type = assembly.GetType("RoslynCompileSample.Writer"); object obj = Activator.CreateInstance(type); type.InvokeMember("Write", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] { "Hello World" });
Roslyn提供了一系列的API來供開發人員通過調用API的方式來創建一個動態程序集,通過API創建動態程序集的方式開發難度大但是編譯效率高,適合需要進行大量動態編譯工作的場景,適合高級開發人員,同樣以上面實現的動態程序集功能為例,下面是通過API的實現:
SyntaxTree syntaxTree = CompilationUnit() .WithUsings( SingletonList<UsingDirectiveSyntax>( UsingDirective( IdentifierName("System")))) .WithMembers( SingletonList<MemberDeclarationSyntax>( NamespaceDeclaration( IdentifierName("RoslynCompileSample")) .WithMembers( SingletonList<MemberDeclarationSyntax>( ClassDeclaration("Writer") .WithModifiers( TokenList( Token(SyntaxKind.PublicKeyword))) .WithMembers( SingletonList<MemberDeclarationSyntax>( MethodDeclaration( PredefinedType( Token(SyntaxKind.VoidKeyword)), Identifier("Write")) .WithModifiers( TokenList( Token(SyntaxKind.PublicKeyword))) .WithParameterList( ParameterList( SingletonSeparatedList<ParameterSyntax>( Parameter( Identifier("message")) .WithType( PredefinedType( Token(SyntaxKind.StringKeyword)))))) .WithBody( Block( SingletonList<StatementSyntax>( ExpressionStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("Console"), IdentifierName("WriteLine"))) .WithArgumentList( ArgumentList( SingletonSeparatedList<ArgumentSyntax>( Argument( IdentifierName("message"))))))))))))))) .NormalizeWhitespace().SyntaxTree; string assemblyName = Path.GetRandomFileName(); MetadataReference[] references = new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location) }; CSharpCompilation compilation = CSharpCompilation.Create( assemblyName, syntaxTrees: new[] { syntaxTree }, references: references, options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); ms.Seek(0, SeekOrigin.Begin); Assembly assembly = Assembly.Load(ms.ToArray()); }
對比兩種實現方式的代碼可以發現,通過API實現的動態編譯就是將原本的所有關鍵字、標識符、連接符、修飾符、表達式等通過API的方式進行描述。
除了關鍵字、連接符、修飾符等API外,Roslyn還提供了包括繼承、特征、約束等相關API,通過API幾乎可以實現任何源碼編譯能實現的所有功能。
具體列子
生成webapi的接口代理
API
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase, IOrder { [HttpGet("{id}")] public Order Add(int id) { return new Order(); } [HttpPut] public Order Addx(string a) { throw new System.NotImplementedException(); } [HttpPut] public Order Update([FromBody] Order value) { return value; } }
動態代理
public class ProxyClass { static readonly IDictionary<string, Type> services = new ConcurrentDictionary<string, Type>(8, 128); static ProxyClass() { PortsImporter.Ports<IService>(); IEnumerable<Type> typeServices = typeof(IService).Assembly.GetTypes().Where(type => { var typeInfo = type.GetTypeInfo(); return typeInfo.IsInterface && typeInfo.GetCustomAttribute<BundleAttribute>() != null; }).ToList(); foreach (var typeService in typeServices) { string code = GetCode(typeService); var assembly = GenerateProxyTree(code); var type = assembly.GetExportedTypes()[0]; var fullName = typeService.FullName; services.Add(fullName, type); } } public static T CreateProxy<T>(Type proxyType, object context) { return (T)Create(proxyType, context); } public static object Create(Type proxyType, object context) { var instance = proxyType.GetTypeInfo().GetConstructors().First().Invoke(null); return instance; } public static T Generate<T>() { if (services.TryGetValue(typeof(T).FullName, out var type)) { return CreateProxy<T>(type, null); } throw new Exception("未找到實現"); } private static string GetCode(Type typeService) { StringBuilder codes = new StringBuilder(); codes.AppendLine("using System;"); codes.AppendLine("using Model;"); codes.AppendLine("using System.Linq;"); codes.AppendFormat("using {0};", typeService.Namespace); codes.AppendLine(); codes.AppendLine("namespace RoslynCompileSample"); codes.AppendLine("{"); codes.AppendFormat("public class Proxy{0} : {1}", typeService.Name, typeService.Name); codes.AppendLine(); codes.AppendLine("{"); var methods = typeService.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (var method in methods) { codes.AppendLine(); codes.AppendFormat("public {0} {1} (", method.ReturnType.FullName, method.Name); List<string> parameterList = new List<string>(); var parameters = method.GetParameters(); foreach (var parameter in parameters) { parameterList.Add($"{parameter.ParameterType.FullName} {parameter.Name}"); } codes.Append(string.Join(',', parameterList)); codes.AppendFormat(")"); codes.AppendLine(); codes.AppendLine("{"); #region 需要自己實現的業務代碼 /*業務*/ if (method.CustomAttributes.Any(item => item.AttributeType == typeof(HttpGetAttribute))) { codes.AppendLine("HttpClientUtility client = new HttpClientUtility(\"http://localhost:57649/api/values\");"); codes.AppendFormat("return client.Get<{0}>(new string[] {{ {1}.ToString() }});", method.ReturnType, parameters.First().Name); } else { codes.AppendLine("return null;"); } #endregion codes.AppendLine("}"); codes.AppendLine(); } codes.AppendLine("}"); codes.AppendLine("}"); return codes.ToString(); } /// <summary> /// 萬能接口 /// </summary> /// <param name="code">傳入你要實現的代碼</param> /// <returns>動態生成一個程序集</returns> public static Assembly GenerateProxyTree(string code) { Assembly assembly = null; SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(code); string assemblyName = Path.GetRandomFileName(); var references = AppDomain.CurrentDomain.GetAssemblies().Select(x => MetadataReference.CreateFromFile(x.Location)); CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, new[] { syntaxTree }, references, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); using (var ms = new MemoryStream()) { EmitResult result = compilation.Emit(ms); if (result.Success) { ms.Seek(0, SeekOrigin.Begin); assembly = Assembly.Load(ms.ToArray()); } } return assembly; } public static void Tets() { //var code = @"using System; namespace RoslynCompileSample { public class Writer { public void Write(string message) { Console.WriteLine(message); } } }"; //var assembly = GenerateProxyTree(code); //Type type = assembly.GetType("RoslynCompileSample.Writer"); //object obj = Activator.CreateInstance(type); //type.InvokeMember("Write", BindingFlags.Default | BindingFlags.InvokeMethod, null, obj, new object[] { "打印一句話" }); } }
測試調用
/*動態編譯*/ var order = ProxyClass.Generate<IOrder>(); var dss = order.Add(2);
github https://github.com/842549829/Roslyn
Roslyn動態編譯的應用場景
用作腳本語言
學習或參與過游戲開發的人基本上都接觸過lua語言,Lua是一個非常小巧的腳本語言,而且很容易嵌入其它語言中使用,很多游戲使用lua作為自己的嵌入式腳本語言來實現劇本演進、特效調用、Mod皮膚等功能,腳本語言有着便於免編譯局部更新的優點,風靡全球長盛不衰的WOW就大量使用了lua腳本語言來開發前段功能。
.Net有了Roslyn后C#、VB.net也具備了腳本語言的優點,不用預先編譯就能夠運行,同時又具備了預編譯語言的特性,執行效率更高,著名的跨平台游戲開發引擎unity/unity3D就已經提供了C#作為腳本開發語言的支持,一年攬金百億的王者榮耀就是基於unity3D引擎開發的。
接口的動態實現
隨着遠程過程調用技術的興起簡化開發過程就成了框架設計開發中需要考慮的重要因素,能像實例化實體類一樣的實例化接口並將接口調用映射到遠程的真實接口實現是最便捷的調用方式,業務開發人員完全不用考慮網絡傳輸、對象序列化、異步操作等遠程
Roslyn動態編譯包含了大量的API,限於篇幅關系,Roslyn動態編譯的API將在后面的章節進行詳細講解。也可以通過在線Roslyn API生成工具(https://roslynquoter.azurewebsites.net/)進行學習。