玩轉動態編譯:四、封裝


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

  • 為什么要封裝?

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

所以我應該重新封裝了一個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;
                }
            }
        }
    }
}
完整DynamicCompile.cs

 demo:

 http://files.cnblogs.com/blqw/%E5%8A%A8%E6%80%81%E7%BC%96%E8%AF%91Demo.rar

..


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM