既然要使用動態編譯,那麼為他封裝一個調用類,在調用時省去大量不必要的編碼操作還是很有必要的 。
為什麼要封裝?
其實這個說起來很簡單,就是發現現有的動態編譯類在使用過程中顯 得並不是那麼好用。我覺得我可以讓他變的更易使用。
所以我應該重新封裝了一個 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