既然要使用動態編譯,那麼為他封裝一個調用類,在調用時省去大量不必要的編碼操作還是很有必要的 。
為什麼要封裝?
其實這個說起來很簡單,就是發現現有的動態編譯類在使用過程中顯 得並不是那麼好用。我覺得我可以讓他變的更易使用。
所以我應該重新封裝了一個 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; } } } } } DynamicCompile_1
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); } CompileClass ,CompileObject
第三個就需要用一些技巧了,但是第三種情況也是使用最多的情 況
我先將方法的代碼外套上一個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:
http://files.cnblogs.com/blqw/%E5%8A%A8%E6%80%81%E7%BC%96%E8%AF%91Demo.rar