Roslyn
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/)进行学习。