程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 玩轉動態編譯(四) 封裝

玩轉動態編譯(四) 封裝

編輯:關於C#

既然要使用動態編譯,那麼為他封裝一個調用類,在調用時省去大量不必要的編碼操作還是很有必要的 。

為什麼要封裝?

其實這個說起來很簡單,就是發現現有的動態編譯類在使用過程中顯 得並不是那麼好用。我覺得我可以讓他變的更易使用。

所以我應該重新封裝了一個 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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved