玩转动态编译:四、封装
既然要使用动态编译,那么为他封装一个调用类,在调用时省去大量不必要的编码操作还是很有必要的。
- 为什么要封装?
其实这个说起来很简单,就是发现现有的动态编译类在使用过程中显得并不是那么好用。我觉得我可以让他变的更易使用。
所以我应该重新封装了一个DynamicCompile类。
不过在这之前我还要考虑一下一个问题:
- 我需要什么?
在使用动态编译的过程中,我逐渐的发现,动态编译有以下几种情况
1.我拼接了一个静态类的代码,需要返回这个类的类型
2.我拼接了一个拥有无参构造函数的类的代码,需要返回这个类的实例
3.我拼接了一个方法代码,需要返回这个方法的委托
对于之前的DynamicCompile_1来说,我要完成这3个工作都需要额外的编写一些重复的,不必要的代码,这对我来说是一件令我很烦躁的事
所以我想要3个方法代替他们
Type type = CompileClass("public static class aaa { public static User GetUser() { new User(); } }", usingTypes); object obj = CompileObject("public class bbb : ICloneable { public object Clone() { return new User(); } }", usingTypes); Func<object, string> func = CompileMethod<Func<object, string>>("public object GetUser() { return new User(); }", usingTypes);
- 封装
先来看看现在的类
using Microsoft.CSharp; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Reflection; using System.Text; namespace blqw { public class DynamicCompile_1 { /// <summary> /// /// </summary> /// <param name="code">需要编译的C#代码</param> /// <param name="usingTypes">编译代码中需要引用的类型</param> /// <returns></returns> public static Assembly CompileAssembly(string code, params Type[] usingTypes) { CompilerParameters compilerParameters = new CompilerParameters();//动态编译中使用的参数对象 compilerParameters.GenerateExecutable = false;//不需要生成可执行文件 compilerParameters.GenerateInMemory = true;//直接在内存中运行 //添加需要引用的类型 HashSet<string> ns = new HashSet<string>();//用来保存命名空间, foreach (var type in usingTypes) { ns.Add("using " + type.Namespace + ";" + Environment.NewLine);//记录命名空间,因为不想重复所以使用了HashSet compilerParameters.ReferencedAssemblies.Add(type.Module.FullyQualifiedName);//这个相当于引入dll } code = string.Concat(ns) + code;//加入using命名空间的代码,即使原来已经有了也不会报错的 //声明编译器 using (CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider()) { //开始编译 CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(compilerParameters, code); if (cr.Errors.HasErrors)//如果有错误 { StringBuilder sb = new StringBuilder(); sb.AppendLine("编译错误:"); foreach (CompilerError err in cr.Errors) { sb.AppendLine(err.ErrorText); } throw new Exception(sb.ToString()); } else { //返回已编译程序集 return cr.CompiledAssembly; } } } } }
CompileAssembly方法依然是需要保留的,只是要增加上说的3个方法
第一个和第二个方法都没有什么难度,他最多只是让我少些几个字符而已
public static Type CompileClass(string code, params Type[] usingTypes) { var ass = CompileAssembly(code, usingTypes); return ass.GetTypes()[0]; } public static object CompileObject(string code, params Type[] usingTypes) { var ass = CompileAssembly(code, usingTypes); return ass.GetTypes()[0].GetConstructors()[0].Invoke(null); }
第三个就需要用一些技巧了,但是第三种情况也是使用最多的情况
我先将方法的代码外套上一个class的外套,然后在class中再写入一个方法
这个方法中将需要编译的方法转换为一个委托后以Object的形式返回
就像这样
//验证方法并获取方法名 private static Regex Regex_Method = new Regex(@"^\s*[\sa-z_]*\s(?<n>[a-z_][a-z0-9_]+)[(](([a-z_][a-z_0-9]*\s+[a-z_][a-z_0-9]*\s*,\s*)*([a-z_][a-z_0-9]*\s+[a-z_][a-z_0-9]*\s*))*[)][\s]*[{][^}]*[}][\s]*\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); //格式化用字符串 private const string FORMATCALSSCODE = @" public class %ClassName:ICloneable { object ICloneable.Clone() { return (%Type)%MethodName; } %Method }"; public static T CompileMethod<T>(string code, params Type[] usingTypes) { var m = Regex_Method.Match(code);//验证方法代码是否可以用 if (m.Success == false) { throw new ArgumentException("code参数有误", "code"); } code = FORMATCALSSCODE .Replace("%ClassName", "_" + Guid.NewGuid().ToString("N")) .Replace("%Type", GetTypeDisplayName(typeof(T))) .Replace("%MethodName", m.Groups["n"].Value) .Replace("%Method", code); var obj = CompileObject(code, usingTypes); return (T)((ICloneable)obj).Clone(); }
- 调用
好了,现在看下第一篇中的栗子,之前需要这样:
public decimal GetValue(string formula) { string code = @" public class Class1 { public static decimal GetValue() { return (decimal)(" + formula + @"); } } "; var ass = DynamicCompile.CompileAssembly(code, typeof(decimal), typeof(string)); return (decimal)ass.GetType("Class1").GetMethod("GetValue").Invoke(null, null); }
而且,这是没有实现接口的,如果要重复调用的话还得写接口.
但是现在我们可以这样写:
public decimal GetValue(string formula) { string code = @" decimal GetValue() { return (decimal)(" + formula + @"); }"; var met = DynamicCompile.CompileMethod<Func<decimal>>(code, typeof(decimal), typeof(string)); return met(); }
他真实生成的代码是这样的
using System; public class _98b6ede1ea204541bc4e709932e6c993:ICloneable { object ICloneable.Clone() { return (System.Func<System.Decimal>)GetValue; } decimal GetValue() { return (decimal)(1+2+3+4+5*6); } }
我们的调用的这样的
var d = GetValue("1+2+3+4+5*6"); Console.WriteLine(d);//结果40
- 最后
好了,动态编译的文章这个算是个结束了
最后这个完成的DynamicCompile.cs就是我现在正在使用的类
动态编译虽然有这那样的好处,可以依然存在无法避免的缺陷
其实之前几篇的评论中就已经有人提到了
1,动态编译的程序集无法卸载,每编译一次就意味着多一份内存消耗,至于消耗多少内存,我是没有统计过;不过这就好比4.0中的匿名类,其实每一个匿名类在编译的时候都会生成一个相应的类,只是这个类不用我们手动去声明,他依然会占用内存,但是即使这样我们依然会大量使用匿名类
2,动态编译一次的性能损耗要远大于反射,所以只有和缓存同时使用,且调用次数大于一定量的时候在性能快于反射,这也是我在第一篇中说的,为什么动态编译一个只使用一次的方式是不明智的原因,所以在使用的时候要注意最好是编译那些会被大量重复调用的方法
3,动态编译还有几个非常致命的缺陷就是难以调试,对于这个情况我的建议就是一开始的时候尽量对一些简单的方法使用,等熟练之后再尝试处理复杂逻辑的方法
PS:在之后介绍 C#对象->Json转换 ,数据实体处理等方面的时候还会用到这个类
- 下载
using Microsoft.CSharp; using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Text.RegularExpressions; namespace blqw { /// <summary> /// 动态编译 /// </summary> public static class DynamicCompile { /// <summary> /// 编译类,并返回已编译类的的类型 /// </summary> public static Type CompileClass(string code, params Type[] usingTypes) { var ass = CompileAssembly(code, usingTypes); return ass.GetTypes()[0]; } /// <summary> /// 编译类,并返回已编译类的实例对象 /// </summary> public static object CompileObject(string code, params Type[] usingTypes) { var ass = CompileAssembly(code, usingTypes); return ass.GetTypes()[0].GetConstructors()[0].Invoke(null); } //验证方法并获取方法名 private static Regex Regex_Method = new Regex(@"^\s*[\sa-z_]*\s(?<n>[a-z_][a-z0-9_]+)[(](([a-z_][a-z_0-9]*\s+[a-z_][a-z_0-9]*\s*,\s*)*([a-z_][a-z_0-9]*\s+[a-z_][a-z_0-9]*\s*))?[)][\s]*[{][^}]*[}][\s]*\s*$", RegexOptions.Compiled | RegexOptions.IgnoreCase); //格式化用字符串 private const string FORMATCALSSCODE = @" public class %ClassName:ICloneable { object ICloneable.Clone() { return (%Type)%MethodName; } %Method }"; /// <summary> /// 编译方法,并返回方法的委托 /// </summary> /// <typeparam name="T">方法委托类型</typeparam> public static T CompileMethod<T>(string code, params Type[] usingTypes) { var m = Regex_Method.Match(code);//验证方法代码是否可以用 if (m.Success == false) { throw new ArgumentException("code参数有误", "code"); } code = FORMATCALSSCODE .Replace("%ClassName", "_" + Guid.NewGuid().ToString("N")) .Replace("%Type", GetTypeDisplayName(typeof(T))) .Replace("%MethodName", m.Groups["n"].Value) .Replace("%Method", code); var obj = CompileObject(code, usingTypes); return (T)((ICloneable)obj).Clone(); } //获取类型的可视化名称 static string GetTypeDisplayName(Type type) { if (type == null) { return "null"; } if (type.IsGenericType) { var arr = type.GetGenericArguments(); string gname = type.GetGenericTypeDefinition().FullName; gname = gname.Remove(gname.IndexOf('`')); if (arr.Length == 1) { return gname + "<" + GetTypeDisplayName(arr[0]) + ">"; } StringBuilder sb = new StringBuilder(gname); sb.Append("<"); foreach (var a in arr) { sb.Append(GetTypeDisplayName(a)); sb.Append(","); } sb[sb.Length - 1] = '>'; return sb.ToString(); } else { return type.FullName; } } /// <summary> /// /// </summary> /// <param name="code">需要编译的C#代码</param> /// <param name="usingTypes">编译代码中需要引用的类型</param> /// <returns></returns> public static Assembly CompileAssembly(string code, params Type[] usingTypes) { CompilerParameters compilerParameters = new CompilerParameters();//动态编译中使用的参数对象 compilerParameters.GenerateExecutable = false;//不需要生成可执行文件 compilerParameters.GenerateInMemory = true;//直接在内存中运行 compilerParameters.IncludeDebugInformation = false; //添加需要引用的类型 Dictionary<string, bool> ns = new Dictionary<string, bool>();//用来保存命名空间, foreach (var type in usingTypes) { ns["using " + type.Namespace + ";" + Environment.NewLine] = true;//记录命名空间,不重复 compilerParameters.ReferencedAssemblies.Add(type.Module.FullyQualifiedName);//这个相当于引入dll } string[] usings = new string[ns.Count]; ns.Keys.CopyTo(usings, 0); code = string.Concat(usings) + code;//加入using命名空间的代码,即使原来已经有了也不会报错的 //声明编译器 using (CSharpCodeProvider objCSharpCodePrivoder = new CSharpCodeProvider()) { //开始编译 CompilerResults cr = objCSharpCodePrivoder.CompileAssemblyFromSource(compilerParameters, code); if (cr.Errors.HasErrors)//如果有错误 { StringBuilder sb = new StringBuilder(); sb.AppendLine("编译错误:"); foreach (CompilerError err in cr.Errors) { sb.AppendLine(err.ErrorText); } throw new Exception(sb.ToString()); } else { //返回已编译程序集 return cr.CompiledAssembly; } } } } }
demo:
https://files.cnblogs.com/blqw/%E5%8A%A8%E6%80%81%E7%BC%96%E8%AF%91Demo.rar
..
我发布的代码,没有任何版权,遵守WTFPL协议(如有引用,请遵守被引用代码的协议)
qq群:5946699 希望各位喜爱C#的朋友可以在这里交流学习,分享编程的心得和快乐