公司原來項目的二次開發方式主要使用SQL,基本上也能滿足客戶的要求,優點是使用簡單,只要熟悉SQL語句就可以操作,缺點是受限制太多,需要對數據庫底層相當的了解,使用時容易出錯,無法直接調用業務層代碼等,研究了一下.net的動態編譯,感覺用它來做二次開發效果應該不錯的。
首先我們先做個demo來解釋一下動態編譯,下面這段代碼的意思就是先組織一個源碼字符串,然後編譯執行。
動態編譯簡單代碼
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
namespace ConsoleApplication6
{
class Program
{
//C#代碼提供者
private static CodeDomProvider comp = new CSharpCodeProvider();
//用於調用編譯器的參數
private static CompilerParameters cp = new CompilerParameters();
private static MethodInfo mi;
static void Main(string[] args)
{
StringBuilder codeBuilder = new StringBuilder();
codeBuilder.AppendLine("using System;");
codeBuilder.AppendLine("public class MyClass");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine("public void hello()");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine("Console.WriteLine( \"hello\");");
codeBuilder.AppendLine("}");
codeBuilder.AppendLine("}");
//加入需要引用的程序集
cp.ReferencedAssemblies.Add("System.dll");
CompilerResults cr = comp.CompileAssemblyFromSource(cp, codeBuilder.ToString());
//如果有編譯錯誤
if (cr.Errors.HasErrors)
{
foreach (CompilerError item in cr.Errors)
{
Console.WriteLine(item.ToString());
}
}
else
{
Assembly a = cr.CompiledAssembly; //獲取已編譯的程序集
Type t = a.GetType("MyClass"); //利用反射獲得類型
object mode = a.CreateInstance("MyClass");
mi = t.GetMethod("hello", BindingFlags.Instance | BindingFlags.Public);
mi.Invoke(mode, new object[0]); //執行方法
}
}
}
}
了解了上面這段代碼,我們基本對動態編譯的概念清楚了,但是如果在項目中這樣使用的話,我們要自己去控制源代碼的全部文檔內容,如果大規模應用的話會非常的麻煩,需要重復編寫命名空間構造,類構造,函數構造等,如果我們還想看到一個格式良好的源碼,我們還必須自己控制格式。現在我們來介紹一種源碼構造方式來解決這些問題,請看如下代碼:
動態編譯簡單代碼改進
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.CodeDom;
using System.IO;
namespace ConsoleApplication6
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(GenerateCode("Hello","Hello","\t\t\treturn \"hello\";"));
}
public static CodeCompileUnit CreateExecutionClass(string typeNamespace,
string typeName,string scriptBody)
{
// 創建CodeCompileUnit以包含代碼
CodeCompileUnit ccu = new CodeCompileUnit();
// 分配需要的命名空間
CodeNamespace cns = new CodeNamespace(typeNamespace);
cns.Imports.Add(new CodeNamespaceImport("System"));
ccu.Namespaces.Add(cns);
// 創建新的類聲明
CodeTypeDeclaration parentClass = new CodeTypeDeclaration(typeName);
cns.Types.Add(parentClass);
// 創建獲得一個參數並返回一個字符串的Hello方法
CodeMemberMethod method = new CodeMemberMethod();
method.Name = "Hello";
method.Attributes = MemberAttributes.Public;
CodeParameterDeclarationExpression arg = new CodeParameterDeclarationExpression(typeof(string), "inputMessage");
method.Parameters.Add(arg);
method.ReturnType = new CodeTypeReference(typeof(string));
// 添加方法實體需要的代碼
CodeSnippetStatement methodBody = new CodeSnippetStatement(scriptBody);
method.Statements.Add(methodBody);
parentClass.Members.Add(method);
return ccu;
}
public static string GenerateCode(string typeNamespace,
string typeName,string scriptBody)
{
// 調用我們前面的方法創建CodeCompileUnit
CodeCompileUnit ccu = CreateExecutionClass(typeNamespace,
typeName, scriptBody); CSharpCodeProvider provider = new CSharpCodeProvider();
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BlankLinesBetweenMembers = false;
options.IndentString = "\t";//指定縮進字符
options.BracingStyle = "C";//大括號換行
StringWriter sw = new StringWriter();
try
{
provider.GenerateCodeFromCompileUnit(ccu, sw, options);
sw.Flush();
}
finally
{
sw.Close();
}
return sw.GetStringBuilder().ToString();
}
}
}
執行結果如下:
大家了解了動態編譯之後,我們這裡介紹一個稍微復雜一點的應用:
需求:我們先預定義一個執行流程,而具體執行代碼可以在我們項目部署之後再編寫。比如說工資的計算在不同應用中算法會有很大的不同。
我們首先定義一個數據庫中的數據結構:
然後將這個表拖入到dbml(生成的dbml文件請下載源碼)文件中,現在我們就開始編寫相應的代碼吧:
首先我們先來處理函數的參數,如果我們只是將參數列表的字符串存入數據庫中的話,我們還要根據格式序列化和反序列化這個參數,所以我們使用xml存入sqlserver2005中,格式如下
參數格式
<Parameters> <Parameter> <Type>System.String</Type> <ParameterName>inputMessage</ParameterName> </Parameter> <Parameter> <Type>System.Int32</Type> <ParameterName>inputInt</ParameterName> </Parameter> </Parameters>
為了方便起見我們在這裡定義一個FunctionScript的分布類來處理參數問題
namespace Phenix.DynamicCompiler { public partial class FunctionScript { /// <summary> /// 返回參數列表 /// </summary> /// <returns></returns> public List<CodeParameterDeclarationExpression> GetParameterList() { List<CodeParameterDeclarationExpression> parameterList = new List<CodeParameterDeclarationExpression>(); if (this.Parameters!=null) { var x = from n in this.Parameters.Elements("Parameter") select n; foreach (var item in x) { parameterList.Add(new CodeParameterDeclarationExpression( Type.GetType((string)item.Element("Type")), (string)item.Element("ParameterName"))); } } return parameterList; } } }
然後我們需要一個代碼構造類,用於根據情況構造代碼:
namespace Phenix.DynamicCompiler { public class CodeBuilder { StringCollection importNameSpace; List<FunctionScript> functionScriptList; string typeNamespace; string className; /// <summary> /// /// </summary> /// <param name="importNameSpace">導入命名空間</param> /// <param name="className">類名</param> /// <param name="functionInfoList">函數列表</param> public CodeBuilder(StringCollection importNameSpace,string typeNamespace,string className, List<FunctionScript> functionScriptList) { if (functionScriptList == null || functionScriptList.Count==0) { throw new Exception("函數列表不能為空"); } this.importNameSpace = importNameSpace; this.typeNamespace = typeNamespace; this.className = className; this.functionScriptList = functionScriptList; if (functionScriptList.GroupBy(c => c.ClassName).Count() > 1) { throw new Exception("這些函數不屬於一個類"); } } public CodeBuilder(string typeNamespace,string className, List<FunctionScript> functionScriptList):this( null,typeNamespace,className,functionScriptList) { } public CodeBuilder(List<FunctionScript> functionScriptList): this(functionScriptList.Count==0?null:functionScriptList[0].ClassNameSpace, functionScriptList.Count == 0 ? null : functionScriptList[0].ClassName, functionScriptList) { } private CodeCompileUnit CreateExecutionClass() { // 創建CodeCompileUnit以包含代碼 CodeCompileUnit ccu = new CodeCompileUnit(); // 分配需要的命名空間 CodeNamespace cns = new CodeNamespace(typeNamespace); cns.Imports.Add(new CodeNamespaceImport("System")); ccu.Namespaces.Add(cns); // 創建新的類聲明 CodeTypeDeclaration codeClass = new CodeTypeDeclaration(className); cns.Types.Add(codeClass); // 在命名空間下加入類 foreach (var functionScript in functionScriptList) { CodeMemberMethod method = new CodeMemberMethod(); method.Name = functionScript.FunctionName; //方法的訪問標識符為public static method.Attributes = MemberAttributes.Static | MemberAttributes.Public; foreach (var parameter in functionScript.GetParameterList()) { method.Parameters.Add(parameter); } if (functionScript.ReturnType != null) { method.ReturnType = new CodeTypeReference(Type.GetType(functionScript.ReturnType)); } // 添加方法實體需要的代碼 CodeSnippetStatement methodBody = new CodeSnippetStatement(functionScript.ScriptBody); method.Statements.Add(methodBody); codeClass.Members.Add(method); } return ccu; } public string GenerateCode() { // 調用我們前面的方法創建CodeCompileUnit CodeCompileUnit ccu = CreateExecutionClass(); CSharpCodeProvider provider = new CSharpCodeProvider(); CodeGeneratorOptions options = new CodeGeneratorOptions(); options.BlankLinesBetweenMembers = false; options.BracingStyle = "C";//大括號換行 options.IndentString = "\t"; StringWriter sw = new StringWriter(); try { provider.GenerateCodeFromCompileUnit(ccu, sw, options); sw.Flush(); } finally { sw.Close(); } return sw.GetStringBuilder().ToString(); } } }
下面我們再編寫一個用於編譯的類:
namespace Phenix.DynamicCompiler { public class PhenixCompiler { /// <summary> /// 編譯 /// </summary> /// <param name="codeString">需要編譯的代碼</param> /// <param name="outputAssembly">輸出程序集位置</param> public void Compile(string codeString, string outputAssembly) { CompilerParameters compilerParams = new CompilerParameters(); ///編譯器選項設置 compilerParams.CompilerOptions = "/target:library /optimize"; compilerParams.OutputAssembly = outputAssembly; ///生成調試信息 compilerParams.IncludeDebugInformation = false; ///添加相關的引用 foreach (var item in ReferencedAssemblies) { compilerParams.ReferencedAssemblies.Add(item); } CSharpCodeProvider provider = new CSharpCodeProvider(); ///編譯 CompilerResults results = provider.CompileAssemblyFromSource(compilerParams, codeString); if (results.Errors.HasErrors) { StringBuilder errors = new StringBuilder(); foreach (CompilerError item in results.Errors) { errors.AppendLine(item.ToString()); } throw new Exception(errors.ToString()); } ///創建程序集 Assembly asm = results.CompiledAssembly; } private StringCollection ReferencedAssemblies { get; set; } public PhenixCompiler(StringCollection referencedAssemblies) { ReferencedAssemblies = referencedAssemblies; ReferencedAssemblies.Add("mscorlib.dll"); ReferencedAssemblies.Add("System.dll"); } public PhenixCompiler() : this(new StringCollection()) { } } }
我們再構造一個用於執行的類:
namespace DynamicCompiler { public class Executor { string inputAssembly; string instanceName; string methodName; public void Execute() { Assembly assembly = Assembly.LoadFrom(inputAssembly); MethodInfo mi; Type t = assembly.GetType(instanceName); object mode = assembly.CreateInstance(instanceName); mi = t.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public); mi.Invoke(mode, new object[0]); } public Executor(string inputAssembly,string instanceName,string methodName) { this.inputAssembly = inputAssembly; this.instanceName = instanceName; this.methodName = methodName; } } }
主函數代碼如下:
主程序
class Program
{
const string functionIndentSpace = "\t\t\t";
static void Main(string[] args)
{
XElement f1parameter = new XElement("Parameters",
new XElement("Parameter",
new XElement("Type", "System.String"),
new XElement("ParameterName", "inputMessage")),
new XElement("Parameter",
new XElement("Type", "System.Int32"),
new XElement("ParameterName", "inputInt"))
);
FunctionScript f1 = new FunctionScript()
{
ClassName = "MyClass",
ClassNameSpace = "My",
FunctionName = "hello",
ReturnType="System.String",
Parameters=f1parameter,
ScriptBody = functionIndentSpace+"return \"hello\"+inputInt.ToString();"
};
FunctionScript f2 = new FunctionScript()
{
ClassName = "MyClass",
ClassNameSpace = "My",
FunctionName = "hello1",
ScriptBody = functionIndentSpace + "Console.WriteLine(hello(\"x\",1));"
};
List<FunctionScript> list=new List<FunctionScript>();
list.Add(f1);
list.Add(f2);
CodeBuilder c = new CodeBuilder(list);
Console.WriteLine(c.GenerateCode());
Compiler pc = new Compiler();
pc.Compile(c.GenerateCode(), "x.dll");
Executor ex = new Executor("x.dll", "My.MyClass", "hello1");
ex.Execute();
}
}
運行結果如下:
這裡邊工作的四個對象的時序圖如下:
源碼下載