博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C#动态编译,实现按钮功能动态配置
阅读量:5830 次
发布时间:2019-06-18

本文共 19555 字,大约阅读时间需要 65 分钟。

hot3.png

现在对做的系统要求要越来越灵活,功能配置越来越方便,牺牲一小部分的效率,而换取系统的灵活性,对于维护、功能扩展升级等工作提供了很大的方便。

前两天,一个项目要求界面上的按钮都是可以配置的,位置和功能都是可配置的。位置好说,用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{    ///        /// 本类用来将字符串转为可执行文本并执行,用于动态定义按钮响应的事件。    ///        public class Evaluator    {        private string filepath = Path.Combine(Application.StartupPath, "FunBtn.config");             #region 构造函数        ///            /// 可执行串的构造函数           ///            ///            /// 可执行字符串数组           ///            public Evaluator(EvaluatorItem[] items)        {            ConstructEvaluator(items);      //调用解析字符串构造函数进行解析           }        ///            /// 可执行串的构造函数           ///            /// 返回值类型           /// 执行表达式           /// 执行字符串名称           public Evaluator(Type returnType, string expression, string name)        {            //创建可执行字符串数组               EvaluatorItem[] items = { new EvaluatorItem(returnType, expression, name) };            ConstructEvaluator(items);      //调用解析字符串构造函数进行解析        }        ///            /// 可执行串的构造函数           ///            /// 可执行字符串项           public Evaluator(EvaluatorItem item)        {            EvaluatorItem[] items = { item };//将可执行字符串项转为可执行字符串项数组               ConstructEvaluator(items);      //调用解析字符串构造函数进行解析           }        ///            /// 解析字符串构造函数           ///            /// 待解析字符串数组           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 公有成员        ///            /// 执行字符串并返回整型值           ///            /// 执行字符串名称           /// 
执行结果
public int EvaluateInt(string name) { return (int)Evaluate(name); } /// /// 执行字符串并返回字符串型值 /// /// 执行字符串名称 ///
执行结果
public string EvaluateString(string name) { return (string)Evaluate(name); } /// /// 执行字符串并返回布尔型值 /// /// 执行字符串名称 ///
执行结果
public bool EvaluateBool(string name) { return (bool)Evaluate(name); } /// /// 执行字符串并返 object 型值 /// /// 执行字符串名称 ///
执行结果
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 静态成员 /// /// 执行表达式并返回整型值 /// /// 要执行的表达式 ///
运算结果
static public int EvaluateToInteger(string code) { Evaluator eval = new Evaluator(typeof(int), code, staticMethodName);//生成 Evaluator 类的对像 return (int)eval.Evaluate(staticMethodName); //执行并返回整型数据 } /// /// 执行表达式并返回字符串型值 /// /// 要执行的表达式 ///
运算结果
static public string EvaluateToString(string code) { Evaluator eval = new Evaluator(typeof(string), code, staticMethodName);//生成 Evaluator 类的对像 return (string)eval.Evaluate(staticMethodName); //执行并返回字符串型数据 } /// /// 执行表达式并返回布尔型值 /// /// 要执行的表达式 ///
运算结果
static public bool EvaluateToBool(string code) { Evaluator eval = new Evaluator(typeof(bool), code, staticMethodName);//生成 Evaluator 类的对像 return (bool)eval.Evaluate(staticMethodName); //执行并返回布尔型数据 } /// /// 执行表达式并返回 object 型值 /// /// 要执行的表达式 ///
运算结果
static public object EvaluateToObject(string code) { Evaluator eval = new Evaluator(typeof(object), code, staticMethodName);//生成 Evaluator 类的对像 return eval.Evaluate(staticMethodName); //执行并返回 object 型数据 } /// /// 执行表达式并返回 void 空值 /// /// 要执行的表达式 static public void EvaluateToVoid(string code) { Evaluator eval = new Evaluator(typeof(void), code, staticMethodName);//生成 Evaluator 类的对像 eval.EvaluateVoid(staticMethodName); //执行并返回 object 型数据 } #endregion #region 私有成员 /// /// 静态方法的执行字符串名称 /// private const string staticMethodName = "ExecuteBtnCommand"; /// /// 用于动态引用生成的类,执行其内部包含的可执行字符串 /// object _Compiled = null; #endregion } /// /// 可执行字符串项(即一条可执行字符串) /// public class EvaluatorItem { /// /// 返回值类型 /// public Type ReturnType; /// /// 执行表达式 /// public string Expression; /// /// 执行字符串名称 /// public string Name; /// /// 可执行字符串项构造函数 /// /// 返回值类型 /// 执行表达式 /// 执行字符串名称 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{    ///        ///   负责读写应用程序配置文件,即app.config的读写。     ///        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        ///         /// 因为不需要多个实例,所以采用了单例模式,由Current属性来获取唯一的实例        ///         /// 文件Url        private Configer(string filepath)        {            this.filePath = filepath;        }        ///         /// 获取当前配置。        ///         /// 配置文件路径        /// 
返回唯一实例
public static Configer Current(string filepath) { if (current == null) { current = new Configer(filepath); } //current = new Configer(filepath); return current; } /// /// 为节点列表的特定属性写入相同的值 /// /// 路径 /// 属性名 public string[] GetAssembly(string path, string nodeProperty) { if (!File.Exists(filePath)) return null; var xmlDoc = new XmlDocument(); xmlDoc.Load(filePath); string[] assemblies = null; //获取节点列表 XmlNodeList nodelist = xmlDoc.SelectNodes(path); if (nodelist != null) { //根据节点个数,实例化数组,用于存在引用的dll名称 assemblies = new string[nodelist.Count]; for (int i = 0; i < nodelist.Count; i++)//遍历列表 { var element = (XmlElement)nodelist[i]; assemblies[i] = element.GetAttribute(nodeProperty);//装载dll到数组中 } } return assemblies;//返回数组 } /// /// 获取节点值 /// /// 节点路径 /// 节点名称 ///
属性值
public string GetNodeProperty(string path, string nodeProperty) { XmlDocument doc = new XmlDocument(); //加载文件 doc.Load(filePath); XmlElement element = null; //节点元素 element = (XmlElement)(doc.SelectSingleNode(path)); if (element != null) { return element.GetAttribute(nodeProperty); } else { return " "; } } /// /// 写入属性值 /// /// 节点路径 /// 属性名称 /// 属性值 public void SetNodeProperty(string path, string nodeProperty, string nodeValue) { var doc = new XmlDocument(); //加载文件 doc.Load(filePath); XmlElement element = null; //获取版本信息 element = (XmlElement)(doc.SelectSingleNode(path)); if (element != null) { //给某个属性写入值 element.SetAttribute(nodeProperty, nodeValue); } doc.Save(filePath); } /// /// 获取节点值 /// /// 节点路径 /// 节点名称 ///
属性值
public string GetNodeValue(string path, string nodeProperty) { XmlDocument doc = new XmlDocument(); //加载文件 doc.Load(filePath); XmlNode node = null; //获取节点 node = (doc.SelectSingleNode(path)); if (node != null) { //返回节点内容 return node.Value; } else { return " "; } } /// /// 写入节点值 /// /// 节点路径 /// 属性名称 /// 属性值 public void SetNodeValue(string path, string nodeProperty, string nodeValue) { var doc = new XmlDocument(); //加载文件 doc.Load(filePath); XmlNode node = null; //获取节点 node = (doc.SelectSingleNode(path)); if (node != null) { //在节点中写入内容 node.Value = nodeValue; } doc.Save(filePath); } /// /// 为节点列表的特定属性写入相同的值 /// /// 路径 /// 属性名 /// 属性值 public void SetAllNodeProperty(string path, string nodeProperty, string nodeValue) { if (!File.Exists(filePath)) return; var xmlDoc = new XmlDocument(); xmlDoc.Load(filePath); //获取节点列表 var selectSingleNode = xmlDoc.SelectNodes(path); if (selectSingleNode != null) { XmlNodeList nodelist = selectSingleNode; foreach (var VARIABLE in nodelist)//遍历列表 { XmlElement element = (XmlElement)VARIABLE; //((XmlElement)VARIABLE).Attributes[nodeProperty].Value = nodeValue; element.SetAttribute(nodeProperty, nodeValue); } } xmlDoc.Save(filePath); } /// /// 创建节点 /// /// 父节点路径 /// 节点名称 /// 属性数组 /// 属性值数组 public void CreateNode(string path,string name, string[] nodeProperties, string[] nodeValues) { if (!File.Exists(filePath)) return; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(filePath); //获取按钮信息设置 var selectSingleNode = xmlDoc.SelectSingleNode(path); if (selectSingleNode != null) { XmlElement xmlElement = xmlDoc.CreateElement(name); //遍历属性数组,一次添加 for (int i = 0; i < nodeProperties.Length; i++) { //添加属性 xmlElement.SetAttribute(nodeProperties[i], nodeValues[i]); } selectSingleNode.AppendChild(xmlElement); } xmlDoc.Save(filePath);//保存 } /// /// 移除节点 /// /// 父节点路径 /// 子节点名称 public void RemoveNode(string path,string name) { if (!File.Exists(filePath)) return; XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(filePath); //获取父节点 var selectSingleNode = xmlDoc.SelectSingleNode(path); if (selectSingleNode != null) { //获取子节点 var node=selectSingleNode.SelectSingleNode(name); if (node != null) { selectSingleNode.RemoveChild(node); } } xmlDoc.Save(filePath);//保存 } }}
配置文件信息如下:FunBtn.Config

这样在系统调用的时候就可以方便多了(我这里所有的按钮都相应一个事件,即Button_Click事件,它们是通过name,在配置文件中找到对应的代码,然后动态编译,然后去执行的)

///         /// 动态执行按钮点击事件        ///         public void Button_Click(object sender, EventArgs e)        {            try            {                Configer configer = Configer.Current(filePath);                //获取调用的代码                string code = configer.GetNodeProperty(                    "FunBtn//btndetail//btn[@name='" + ((Button)sender).Name + "']", "code");                                //获取调用方法的返回值类型                string returnType =                    configer.GetNodeProperty("FunBtn//btndetail//btn[@name='" + ((Button)sender).Name + "']",                                             "returntype");                //根据返回值类型,判断调用那个动态生成的接口                switch (returnType)                {                    case "void":                        Evaluator.EvaluateToVoid(code);                        break;                    case "object":                        Evaluator.EvaluateToObject(code);                        break;                    case "int":                        Evaluator.EvaluateToInteger(code);                        break;                    case "bool":                        Evaluator.EvaluateToBool(code);                        break;                    case "string":                        Evaluator.EvaluateToString(code);                        break;                }            }            catch (Exception exception)            {                MessageBox.Show(exception.Message);            }        }

这样,只要你配置文件中的加入了程序集和命名控件,且代码OK,那么你就能调用任意的dll了。意味着你拥有了强大的扩展性,在添加功能方面,不用再重新发布新版本了。直接放用到的dll放入到程序根目录下,然后配置正确FunBtn.config即可。

当然,其实我还是不推荐用动态编译的,因为影响效率呀。每次点击按钮,都需要重新动态编译,效率肯定是要降低的。所以你在借鉴时,一定要慎重选择哦。

版权声明:本文为博主原创文章,未经博主允许不得转载。

转载于:https://my.oschina.net/u/2260184/blog/518446

你可能感兴趣的文章
ASP.NET Page Events Lifecycle
查看>>
Android显示系统设计框架介绍
查看>>
知其所以然(以算法学习为例)
查看>>
ORacle初级题
查看>>
郑捷《机器学习算法原理与编程实践》学习笔记(第三章 决策树的发展)(一 )_ID3...
查看>>
centos7+cobbler安装
查看>>
android 4.x环境搭建
查看>>
iOS 内存泄漏
查看>>
关于JSONP和JSON(一篇写的不错的文章)
查看>>
贪吃蛇“大作战”(终结篇)
查看>>
Morris图表如何重新加载数据(重绘)
查看>>
数据库面试(二)
查看>>
mybatis笔记
查看>>
C#实现微信开发前奏
查看>>
测试日志windows live writer
查看>>
第五次作业
查看>>
今天介绍一个渐变的方法,在shell里面自动生成注释简介
查看>>
Unity教程之-Unity3d中针对Android Apk的签名验证(C#实现)
查看>>
Linux的起源、特点和版本号
查看>>
Java API ——String类
查看>>