首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

動態編譯封裝庫 DynamicCompile:三大場景一行搞定,支援快取與委派零反射調用

     編輯:關於C#

前三篇已掌握 CSharpCodeProvider 基礎用法、實戰 JSON 序列化、拋棄反射改用介面零反射調用。本文將其封裝為靜態類別 DynamicCompile,提供三個高階 API 覆蓋 90% 場景,並內建命名空間去重、除錯資訊關閉、泛型委派回傳型別名稱解析等細節優化。

三大核心 API

public static class DynamicCompile {
    // 1. 編譯類別 → 回傳 Type(適用靜態類別、單例模式)
    public static Type CompileClass(string code, params Type[] usingTypes);

    // 2. 編譯類別 → 回傳實例(適用有無參建構子的類別)
    public static object CompileObject(string code, params Type[] usingTypes);

    // 3. 編譯方法 → 回傳強型別委派(最常用、效能最佳)
    public static T CompileMethod<T>(string code, params Type[] usingTypes) where T : Delegate;
}

CompileMethod 核心技巧:包裝成 ICloneable 取得委派

動態編譯出的方法無法直接轉委派,作者巧妙包裝:

  1. 用 Regex 擷取方法名稱
  2. 將使用者代碼包裝在實作 ICloneable 的類別中
  3. Clone() 回傳 (T)methodName,隱式轉換為委派
  4. 編譯該類別 → CompileObject 取得實例 → ((ICloneable)obj).Clone() 即得強型別委派
private const string FORMAT_CLASS = @"
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) throw new ArgumentException("code 參數格式錯誤");

    code = FORMAT_CLASS
        .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();
}

實際呼叫對比

// 舊寫法:手動拼類別、反射取 MethodInfo、Invoke
public decimal GetValue(string formula) {
    string code = $@"public class Class1 {{ public static decimal GetValue() {{ return (decimal)({formula}); }} }}";
    var ass = DynamicCompile_1.CompileAssembly(code, typeof(decimal));
    return (decimal)ass.GetType("Class1").GetMethod("GetValue").Invoke(null, null);
}

// 新寫法:只寫方法本體,直接得委派
public decimal GetValue(string formula) {
    string code = $@"decimal GetValue() {{ return (decimal)({formula}); }}";
    var func = DynamicCompile.CompileMethod<Func<decimal>>(code, typeof(decimal));
    return func(); // 零反射,直接調用
}

真實產生的代碼:

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); }
}

完整 DynamicCompile.cs

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 {
    public static class DynamicCompile {
        private static readonly Regex Regex_Method = new(
            @"^\s*[\w\s]*\s(?\w+)\s*\(([^)]*)\)\s*\{[^}]*\}\s*$",
            RegexOptions.Compiled | RegexOptions.IgnoreCase);

        private const string FORMAT_CLASS = @"
public class %ClassName : ICloneable {
    object ICloneable.Clone() { return (%Type)%MethodName; }
    %Method
}";

        public static Type CompileClass(string code, params Type[] usingTypes)
            => CompileAssembly(code, usingTypes).GetTypes()[0];

        public static object CompileObject(string code, params Type[] usingTypes)
            => CompileAssembly(code, usingTypes).GetTypes()[0].GetConstructors()[0].Invoke(null);

        public static T CompileMethod<T>(string code, params Type[] usingTypes) where T : Delegate {
            var m = Regex_Method.Match(code);
            if (!m.Success) throw new ArgumentException("code 參數格式錯誤");

            code = FORMAT_CLASS
                .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();
        }

        private static string GetTypeDisplayName(Type type) {
            if (type == null) return "null";
            if (!type.IsGenericType) return type.FullName;

            var args = type.GetGenericArguments();
            string gname = type.GetGenericTypeDefinition().FullName;
            gname = gname.Remove(gname.IndexOf('`'));
            if (args.Length == 1) return gname + "<" + GetTypeDisplayName(args[0]) + ">";

            var sb = new StringBuilder(gname).Append("<");
            foreach (var a in args) { sb.Append(GetTypeDisplayName(a)).Append(","); }
            sb[sb.Length - 1] = '>';
            return sb.ToString();
        }

        public static Assembly CompileAssembly(string code, params Type[] usingTypes) {
            var cp = new CompilerParameters {
                GenerateExecutable = false,
                GenerateInMemory = true,
                IncludeDebugInformation = false
            };
            var ns = new Dictionary<string, bool>();
            foreach (var t in usingTypes) {
                ns["using " + t.Namespace + ";" + Environment.NewLine] = true;
                cp.ReferencedAssemblies.Add(t.Module.FullyQualifiedName);
            }
            code = string.Concat(ns.Keys) + code;

            using (var provider = new CSharpCodeProvider()) {
                var cr = provider.CompileAssemblyFromSource(cp, code);
                if (cr.Errors.HasErrors) {
                    var sb = new StringBuilder("編譯錯誤:");
                    foreach (CompilerError err in cr.Errors) sb.AppendLine(err.ErrorText);
                    throw new Exception(sb.ToString());
                }
                return cr.CompiledAssembly;
            }
        }
    }
}

三大缺陷與對策(必讀)

缺陷影響對策
組件無法卸載每次編譯永久佔用記憶體只編譯「高頻重複呼叫」的熱路徑;冷路徑用反射
首次編譯慢比反射慢數倍啟動時預編譯、快取委派、搭配 ConcurrentDictionary
難以除錯動態代碼無法設中斷點先用靜態代碼驗證邏輯,成熟後再動態化

封裝完成的 DynamicCompile 已在作者工具庫服役多年,後續 JSON 轉換器、資料實體處理皆建立於此。源碼與 Demo 見文末連結。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved