現在對做的系統要求要越來越靈活,功能配置越來越方便,犧牲一小部分的效率,而換取系統的靈活性,對於維護、功能擴展升級等工作提供了很大的方便。
前兩天,一個項目要求界面上的按鈕都是可以配置的,位置和功能都是可配置的。位置好說,用xml即可。但是功能可配置就有點難度了。如果說使用接口,那麼參數則不好設置,而且就算用接口,在實際調用時,也得明確實例化哪個類。您可能還會說用反射,嗯,這的確是個好辦法,但是還是在調用的時候,參數不確定,反射也就無用武之地了。查了半天,最終還是選擇了動態編譯。
用一個專門的類,完成動態編譯的過程。其實這個動態編譯,就是動態生成代碼,經過動態編譯,然後直接在系統中可以使用。所以需要你在代碼中添加中功能所需要的動態鏈接庫、程序集以及命名空間。以下是我用到的動態編譯的類:
using System; using System.Data; using System.Configuration; using System.IO; using System.Text; using System.CodeDom.Compiler; using System.Windows.Forms; using Microsoft.CSharp; using System.Reflection; namespace DynamicAddFunction { /// <summary> /// 本類用來將字符串轉為可執行文本並執行,用於動態定義按鈕響應的事件。 /// </summary> public class Evaluator { private string filepath = Path.Combine(Application.StartupPath, "FunBtn.config"); #region 構造函數 /// <summary> /// 可執行串的構造函數 /// </summary> /// <param name="items"> /// 可執行字符串數組 /// </param> public Evaluator(EvaluatorItem[] items) { ConstructEvaluator(items); //調用解析字符串構造函數進行解析 } /// <summary> /// 可執行串的構造函數 /// </summary> /// <param name="returnType">返回值類型</param> /// <param name="expression">執行表達式</param> /// <param name="name">執行字符串名稱</param> public Evaluator(Type returnType, string expression, string name) { //創建可執行字符串數組 EvaluatorItem[] items = { new EvaluatorItem(returnType, expression, name) }; ConstructEvaluator(items); //調用解析字符串構造函數進行解析 } /// <summary> /// 可執行串的構造函數 /// </summary> /// <param name="item">可執行字符串項</param> public Evaluator(EvaluatorItem item) { EvaluatorItem[] items = { item };//將可執行字符串項轉為可執行字符串項數組 ConstructEvaluator(items); //調用解析字符串構造函數進行解析 } /// <summary> /// 解析字符串構造函數 /// </summary> /// <param name="items">待解析字符串數組</param> private void ConstructEvaluator(EvaluatorItem[] items) { //創建C#編譯器實例 //ICodeCompiler comp = (new CSharpCodeProvider().CreateCompiler()); CSharpCodeProvider comp = new CSharpCodeProvider(); //編譯器的傳入參數 CompilerParameters cp = new CompilerParameters(); Configer configer = Configer.Current(filepath); string[] assemblies = configer.GetAssembly("FunBtn//assembly//dll","name"); cp.ReferencedAssemblies.AddRange(assemblies); //添加程序集集合 //cp.ReferencedAssemblies.Add("system.dll"); //添加程序集 system.dll 的引用 //cp.ReferencedAssemblies.Add("system.data.dll"); //添加程序集 system.data.dll 的引用 //cp.ReferencedAssemblies.Add("system.xml.dll"); //添加程序集 system.xml.dll 的引用 //cp.ReferencedAssemblies.Add("system.windows.forms.dll"); //cp.ReferencedAssemblies.Add("FunButton.dll"); //cp.ReferencedAssemblies.Add("DynamicAddFunction.exe"); cp.GenerateExecutable = false; //不生成可執行文件 cp.GenerateInMemory = true; //在內存中運行 StringBuilder code = new StringBuilder(); //創建代碼串 /* * 添加常見且必須的引用字符串 */ //獲取引用的命名空間 string[] usings = configer.GetAssembly("FunBtn//assembly//using", "name"); foreach (var @using in usings) { code.Append(@using+"\n");//添加引用的命名空間 } //code.Append("using System; \n"); //code.Append("using System.Data; \n"); //code.Append("using System.Data.SqlClient; \n"); //code.Append("using System.Data.OleDb; \n"); //code.Append("using System.Xml; \n"); //code.Append("using FunButton; \n"); //code.Append("using System.Windows.Forms; \n"); //code.Append("using DynamicAddFunction; \n"); code.Append("namespace EvalGuy { \n"); //生成代碼的命名空間為EvalGuy,和本代碼一樣 code.Append(" public class _Evaluator { \n"); //產生 _Evaluator 類,所有可執行代碼均在此類中運行 foreach (EvaluatorItem item in items) //遍歷每一個可執行字符串項 { code.AppendFormat(" public {0} {1}() ", //添加定義公共函數代碼 item.ReturnType.Name.ToLower() , //函數返回值為可執行字符串項中定義的返回值類型 item.Name); //函數名稱為可執行字符串項中定義的執行字符串名稱 code.Append("{ "); //添加函數開始括號 if (item.ReturnType.Name == "Void") { code.AppendFormat("{0};", item.Expression);//添加函數體,返回可執行字符串項中定義的表達式的值 } else { code.AppendFormat("return ({0});", item.Expression);//添加函數體,返回可執行字符串項中定義的表達式的值 } code.Append("}\n"); //添加函數結束括號 } code.Append("} }"); //添加類結束和命名空間結束括號 //得到編譯器實例的返回結果 CompilerResults cr = comp.CompileAssemblyFromSource(cp, code.ToString()); if (cr.Errors.HasErrors) //如果有錯誤 { StringBuilder error = new StringBuilder(); //創建錯誤信息字符串 error.Append("編譯有錯誤的表達式: "); //添加錯誤文本 foreach (CompilerError err in cr.Errors) //遍歷每一個出現的編譯錯誤 { error.AppendFormat("{0}\n", err.ErrorText); //添加進錯誤文本,每個錯誤後換行 } throw new Exception("編譯錯誤: " + error.ToString());//拋出異常 } Assembly a = cr.CompiledAssembly; //獲取編譯器實例的程序集 _Compiled = a.CreateInstance("EvalGuy._Evaluator"); //通過程序集查找並聲明 EvalGuy._Evaluator 的實例 } #endregion #region 公有成員 /// <summary> /// 執行字符串並返回整型值 /// </summary> /// <param name="name">執行字符串名稱</param> /// <returns>執行結果</returns> public int EvaluateInt(string name) { return (int)Evaluate(name); } /// <summary> /// 執行字符串並返回字符串型值 /// </summary> /// <param name="name">執行字符串名稱</param> /// <returns>執行結果</returns> public string EvaluateString(string name) { return (string)Evaluate(name); } /// <summary> /// 執行字符串並返回布爾型值 /// </summary> /// <param name="name">執行字符串名稱</param> /// <returns>執行結果</returns> public bool EvaluateBool(string name) { return (bool)Evaluate(name); } /// <summary> /// 執行字符串並返 object 型值 /// </summary> /// <param name="name">執行字符串名稱</param> /// <returns>執行結果</returns> public object Evaluate(string name) { MethodInfo mi = _Compiled.GetType().GetMethod(name);//獲取 _Compiled 所屬類型中名稱為 name 的方法的引用 return mi.Invoke(_Compiled, null); //執行 mi 所引用的方法 } public void EvaluateVoid(string name) { MethodInfo mi = _Compiled.GetType().GetMethod(name);//獲取 _Compiled 所屬類型中名稱為 name 的方法的引用 mi.Invoke(_Compiled, null); //執行 mi 所引用的方法 } #endregion #region 靜態成員 /// <summary> /// 執行表達式並返回整型值 /// </summary> /// <param name="code">要執行的表達式</param> /// <returns>運算結果</returns> static public int EvaluateToInteger(string code) { Evaluator eval = new Evaluator(typeof(int), code, staticMethodName);//生成 Evaluator 類的對像 return (int)eval.Evaluate(staticMethodName); //執行並返回整型數據 } /// <summary> /// 執行表達式並返回字符串型值 /// </summary> /// <param name="code">要執行的表達式</param> /// <returns>運算結果</returns> static public string EvaluateToString(string code) { Evaluator eval = new Evaluator(typeof(string), code, staticMethodName);//生成 Evaluator 類的對像 return (string)eval.Evaluate(staticMethodName); //執行並返回字符串型數據 } /// <summary> /// 執行表達式並返回布爾型值 /// </summary> /// <param name="code">要執行的表達式</param> /// <returns>運算結果</returns> static public bool EvaluateToBool(string code) { Evaluator eval = new Evaluator(typeof(bool), code, staticMethodName);//生成 Evaluator 類的對像 return (bool)eval.Evaluate(staticMethodName); //執行並返回布爾型數據 } /// <summary> /// 執行表達式並返回 object 型值 /// </summary> /// <param name="code">要執行的表達式</param> /// <returns>運算結果</returns> static public object EvaluateToObject(string code) { Evaluator eval = new Evaluator(typeof(object), code, staticMethodName);//生成 Evaluator 類的對像 return eval.Evaluate(staticMethodName); //執行並返回 object 型數據 } /// <summary> /// 執行表達式並返回 void 空值 /// </summary> /// <param name="code">要執行的表達式</param> static public void EvaluateToVoid(string code) { Evaluator eval = new Evaluator(typeof(void), code, staticMethodName);//生成 Evaluator 類的對像 eval.EvaluateVoid(staticMethodName); //執行並返回 object 型數據 } #endregion #region 私有成員 /// <summary> /// 靜態方法的執行字符串名稱 /// </summary> private const string staticMethodName = "ExecuteBtnCommand"; /// <summary> /// 用於動態引用生成的類,執行其內部包含的可執行字符串 /// </summary> object _Compiled = null; #endregion } /// <summary> /// 可執行字符串項(即一條可執行字符串) /// </summary> public class EvaluatorItem { /// <summary> /// 返回值類型 /// </summary> public Type ReturnType; /// <summary> /// 執行表達式 /// </summary> public string Expression; /// <summary> /// 執行字符串名稱 /// </summary> public string Name; /// <summary> /// 可執行字符串項構造函數 /// </summary> /// <param name="returnType">返回值類型</param> /// <param name="expression">執行表達式</param> /// <param name="name">執行字符串名稱</param> public EvaluatorItem(Type returnType, string expression, string name) { ReturnType = returnType; Expression = expression; Name = name; } } }
為了提高其靈活性,上面這個類添加的程序集和命名空間,以及調用功能的代碼,都改成了讀取xml格式的配置文件來獲取。這樣做理論上可以使得系統中可以添加任意.net制作的dll、exe等的功能。大大增強了系統的靈活性。
讀寫配置文件的類采用了單例模式,可以減少對系統的消耗,代碼如下:
using System; using System.Drawing; using System.IO; using System.Windows.Forms; using System.Xml; using System.Reflection; using System.Configuration; namespace DynamicAddFunction { /// <summary> /// 負責讀寫應用程序配置文件,即app.config的讀寫。 /// </summary> public class Configer { #region 私有成員 private string version; //版本號 private string updatetime; //更新時間 private string isautoupdate; //是否自動更新 private string funbtnwidth; //功能區按鈕寬度 private string funbtnheight; //功能區按鈕高度 private string expandbtnwidth; //擴展區按鈕寬度 private string expandbtnheight; //擴展區按鈕高度 //private string btnname; //按鈕名稱 //private string btntext; //按鈕顯示文本 //private string btntype; //按鈕類型 //private string btnleft; //左邊距 //private string btntop; //上邊距 //private string btnreturntype; //返回值類型 //private string btncode; //調用代碼 private string arrange; //擴展區按鈕布局 private string firstleft; //第一個按鈕的左邊距 private string firsttop; //第一個按鈕的上邊距 private string horizontal; //水平間距 private string vertical; //垂直間距 private string filePath; //文件路徑 private static Configer current; //唯一實例 #endregion /// <summary> /// 因為不需要多個實例,所以采用了單例模式,由Current屬性來獲取唯一的實例 /// 查看本欄目
<?xml version="1.0" encoding="utf-8"?> <FunBtn> <!-- 版本信息 --> <info version="1.00" updatetime="2013-01-01 10:00:00.123" isautoupdate="true" /> <!-- 引用的程序集和命名空間 --> <assembly> <dll name="system.dll" /> <dll name="system.data.dll" /> <dll name="system.xml.dll" /> <dll name="system.windows.forms.dll" /> <dll name="FunButton.dll" /> <dll name="DynamicAddFunction.exe" /> <using name="using System;" /> <using name="using System.Data;" /> <using name="using System.Data.SqlClient;" /> <using name="using System.Data.OleDb;" /> <using name="using System.Xml;" /> <using name="using FunButton;" /> <using name="using System.Windows.Forms;" /> <using name="using DynamicAddFunction;" /> </assembly> <!-- 按鈕屬性和方法 --> <btndetail> <btn name="btnDelOrder" value="刪除訂單" type="funbtn" rank="0" left="10" top="5" returntype="void" code="new DelOrderBtn().DelOrder(Form1.objs)" /> <btn name="btnCheckOut" value="結 賬" type="funbtn" rank="-1" left="10" top="335" returntype="void" code="new CheckOutBtn().CheckOut(Form1.objs)" /> <btn name="btnClose" value="退出" type="funbtn" rank="-1" left="240" top="335" returntype="void" code="Application.Exit()" /> <btn name="btnDelAllOrder" value="整單刪除" type="funbtn" rank="-1" left="240" top="225" returntype="void" code="new DelOrderBtn().DelOrder(Form1.objs)" /> <btn name="btnMore" value="更 多" type="funbtn" rank="6" left="10" top="230" returntype="void" code="new frmMore().Show()" /> </btndetail> </FunBtn>這樣在系統調用的時候就可以方便多了(我這裡所有的按鈕都相應一個事件,即Button_Click事件,它們是通過name,在配置文件中找到對應的代碼,然後動態編譯,然後去執行的)
/// <summary> /// 動態執行按鈕點擊事件 /// </summary> public void Button_Click(object sender, EventArgs e) { try { Configer configer = Configer.Current(filePath); //獲取調用的代碼 // 查看本欄目