Fireasy3 揭秘 -- 使用 Emit 構建程式集

来源:https://www.cnblogs.com/fireasy/archive/2023/03/12/17201880.html
-Advertisement-
Play Games

在運行期間,我們可以使用 `Emit` 來組織一段 IL 代碼,進而動態生成一個方法,甚至是一個程式集(包括類型、方法或屬性等等)。這個過程我們稱之為動態編織。這一項技術應用比較廣泛,比如數據映射(Dapper)、動態代理(AOP)等等,目的是提升大量反射而產生的性能問題。 ...


目錄

  • Fireasy3 揭秘 -- 依賴註入與服務發現
  • Fireasy3 揭秘 -- 自動服務部署
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 改進服務發現
  • Fireasy3 揭秘 -- 使用 SourceGeneraor 實現動態代理(AOP)
  • Fireasy3 揭秘 -- 使用 Emit 構建程式集
  • Fireasy3 揭秘 -- 使用緩存提高反射性能
  • Fireasy3 揭秘 -- 動態類型及擴展支持
  • Fireasy3 揭秘 -- 線程數據共用的實現
  • Fireasy3 揭秘 -- 配置管理及解析處理
  • Fireasy3 揭秘 -- 資料庫適配器
  • Fireasy3 揭秘 -- 解決資料庫之間的語法差異
  • Fireasy3 揭秘 -- 獲取資料庫的架構信息
  • Fireasy3 揭秘 -- 數據批量插入的實現
  • Fireasy3 揭秘 -- 使用包裝器對數據讀取進行相容
  • Fireasy3 揭秘 -- 數據行映射器
  • Fireasy3 揭秘 -- 數據轉換器的實現
  • Fireasy3 揭秘 -- 通用序列生成器和雪花生成器的實現
  • Fireasy3 揭秘 -- 命令攔截器的實現
  • Fireasy3 揭秘 -- 資料庫主從同步的實現
  • Fireasy3 揭秘 -- 大數據分頁的策略
  • Fireasy3 揭秘 -- 數據按需更新及生成實體代理類
  • Fireasy3 揭秘 -- 用對象池技術管理上下文
  • Fireasy3 揭秘 -- Lambda 表達式解析的原理
  • Fireasy3 揭秘 -- 擴展選擇的實現
  • Fireasy3 揭秘 -- 按需載入與惰性載入的區別與實現
  • Fireasy3 揭秘 -- 自定義函數的解析與綁定
  • Fireasy3 揭秘 -- 與 MongoDB 進行適配
  • Fireasy3 揭秘 -- 模塊化的實現原理

  在運行期間,我們可以使用 Emit 來組織一段 IL 代碼,進而動態生成一個方法,甚至是一個程式集(包括類型、方法或屬性等等)。這個過程我們稱之為動態編織。這一項技術應用比較廣泛,比如數據映射(Dapper)、動態代理(AOP)等等,目的是提升大量反射而產生的性能問題。
  在 Fireasy 里,提供了以下幾個構造器,用於生成一個完整的程式集:

  • DynamicAssemblyBuilder 動態程式集構造器
  • DynamicTypeBuilder 動態類型構造器
  • DynamicInterfaceBuilder 動態介面構造器
  • DynamicEnumBuilder 動態枚舉構造器
  • DynamicFieldBuilder 動態欄位域構造器
  • DynamicPropertyBuilder 動態屬性構造器
  • DynamicConstructorBuilder 動態構造函數構造器
  • DynamicMethodBuilder 動態方法構造器

  接下來,我會對每個構造器進行介紹,以瞭解它們的基本原理。

DynamicAssemblyBuilder

  動態程式集構造器是一個起點,你只有先創建了一個程式集,才能在程式集里定義各種介面、類型。
  其實它的核心是在當前程式域內定義一個 AssemblyBuilder,再此基礎上再定義一個 ModuleBuilder,這是程式集內部的結構,它們都來自於 System.Reflection.Emit 命名空間下。如下:

    public class DynamicAssemblyBuilder : DynamicBuilder
    {
        private AssemblyBuilder _assemblyBuilder;
        private ModuleBuilder _moduleBuilder;

        private AssemblyBuilder InitAssemblyBuilder()
        {
            if (_assemblyBuilder == null)
            {
                var an = new AssemblyName(AssemblyName);
                if (string.IsNullOrEmpty(OutputAssembly))
                {
#if NETFRAMEWORK
                    _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
#else
                    _assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndCollect);
#endif
                }
                else
                {
#if NETFRAMEWORK
                    var dir = Path.GetDirectoryName(OutputAssembly);
                    if (!Directory.Exists(dir))
                    {
                        Directory.CreateDirectory(dir);
                    }

                    _assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave, dir);
#else
                    _assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
#endif
                }
            }

            return _assemblyBuilder;
        }

        /// <summary>
        /// 獲取 <see cref="ModuleBuilder"/> 對象。
        /// </summary>
        /// <returns></returns>
        private ModuleBuilder InitModuleBuilder()
        {
            if (_moduleBuilder == null)
            {
                if (string.IsNullOrEmpty(OutputAssembly))
                {
                    _moduleBuilder = AssemblyBuilder.DefineDynamicModule("Main");
                }
                else
                {
                    var fileName = OutputAssembly.Substring(OutputAssembly.LastIndexOf("\\") + 1);
#if NETFRAMEWORK
                    _moduleBuilder = AssemblyBuilder.DefineDynamicModule(fileName, fileName);
#else
                    _moduleBuilder = AssemblyBuilder.DefineDynamicModule(fileName);
#endif
                }
            }

            return _moduleBuilder;
        }
    }

  在 .net framework 時代,我們可以將動態構造的程式集存儲到磁碟中,很遺憾,從 .net standard 之後就無法實現這一功能了,這給我們帶來了諸多不便,因為動態生成的程式集必須存儲在記憶體當中了。

  然後,DynamicAssemblyBuilder 提供了定義介面、類型和枚舉的方法,將工作交給這些不同的構造器。如下:

        /// <summary>
        /// 使用當前的構造器定義一個動態類型。
        /// </summary>
        /// <param name="typeName">類型的名稱。</param>
        /// <param name="accessibility">指定類的可見性。</param>
        /// <param name="modifier">指定類的調用屬性。</param>
        /// <param name="baseType">類型的父類。</param>
        /// <returns></returns>
        public DynamicTypeBuilder DefineType(string typeName, Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, Type baseType = null)
        {
            var typeBuilder = new DynamicTypeBuilder(Context, typeName, accessibility, modifier, baseType);
            _typeBuilders.Add(typeBuilder);
            return typeBuilder;
        }

        /// <summary>
        /// 使用當前的構造器定義一個動態介面。
        /// </summary>
        /// <param name="typeName">類型的名稱。</param>
        /// <param name="accessibility">指定類的可見性。</param>
        /// <returns></returns>
        public DynamicInterfaceBuilder DefineInterface(string typeName, Accessibility accessibility = Accessibility.Public)
        {
            var typeBuilder = new DynamicInterfaceBuilder(Context, typeName, accessibility);
            _typeBuilders.Add(typeBuilder);
            return typeBuilder;
        }

        /// <summary>
        /// 使用當前構造器定義一個枚舉。
        /// </summary>
        /// <param name="enumName">枚舉的名稱。</param>
        /// <param name="underlyingType">枚舉的類型。</param>
        /// <param name="accessibility">指定枚舉的可見性。</param>
        /// <returns></returns>
        public DynamicEnumBuilder DefineEnum(string enumName, Type? underlyingType = null, Accessibility accessibility = Accessibility.Public)
        {
            var enumBuilder = new DynamicEnumBuilder(Context, enumName, underlyingType ?? typeof(int), accessibility);
            _typeBuilders.Add(enumBuilder);
            return enumBuilder;
        }

  所以 DynamicAssemblyBuilder 最多只能算是一個容器,更多的工作分解到各種構造器中。上面的方法中,定義介面、類型、枚舉時,都將相應的構造器放到 _typeBuilders 集合里,在最後調用 Create 方法逐一創建。如下:

        /// <summary>
        /// 創建程式集。
        /// </summary>
        /// <returns></returns>
        public Assembly Create()
        {
            if (!_isCreated)
            {
                foreach (var typeBuilder in _typeBuilders)
                {
                    typeBuilder.CreateType();
                }

                _isCreated = true;
            }

            return AssemblyBuilder;
        }

DynamicTypeBuilder

  動態類型構造器是對 TypeBuilder 的包裝。這裡需要強調的是,在 DefineType 時,類的訪問性由 Accessibility 枚舉來指定,如 public、private、protected、internal 等等,修飾性則由 Modifier 枚舉來指定,如 abstract、sealed。以下的方法主要用來設定類型的的 TypeAttributes,如下:

        private TypeAttributes GetTypeAttributes(Accessibility accessibility, Modifier modifier)
        {
            var attrs = GetTypeAttributes();
            switch (modifier)
            {
                case Modifier.Abstract:
                    attrs |= TypeAttributes.Abstract;
                    break;
                case Modifier.Sealed:
                    attrs |= TypeAttributes.Sealed;
                    break;
            }

            switch (accessibility)
            {
                case Accessibility.Internal:
                    if (_isNesetType)
                    {
                        attrs |= TypeAttributes.NestedAssembly;
                    }

                    break;
                case Accessibility.Private:
                    if (_isNesetType)
                    {
                        attrs |= TypeAttributes.NestedPrivate;
                    }

                    break;
                case Accessibility.Public:
                    attrs |= _isNesetType ? TypeAttributes.NestedPublic : TypeAttributes.Public;
                    break;
            }

            return attrs;
        }

  這裡重點要介紹一下泛型類型參數的處理。為了相容定義方法時的泛型類型參數,這裡定義了一個 GtpType 類型(Generic Type Parameter),它繼承自 Type 類型,主要用到 Name 屬性,其他屬性均忽略。另外,通過以下的方法設定約束:

    /// <summary>
    /// 用於標識一個泛型類型參數的類型。無法繼承此類。
    /// </summary>
    public sealed class GtpType : Type
    {
        private Type? _baseType;
        private Type[]? _constraintTypes;
        private GenericParameterAttributes? _parameterAttributes;

        /// <summary>
        /// 設置基類約束。
        /// </summary>
        /// <param name="baseType">基類類型。</param>
        /// <returns></returns>
        public GtpType SetBaseTypeConstraint(Type baseType)
        {
            _baseType = baseType;
            return this;
        }

        /// <summary>
        /// 設置介面約束。
        /// </summary>
        /// <param name="constraintTypes">約束類型。</param>
        /// <returns></returns>
        public GtpType SetInterfaceConstraints(params Type[] constraintTypes)
        {
            _constraintTypes = constraintTypes;
            return this;
        }

        /// <summary>
        /// 設置參數特性。
        /// </summary>
        /// <param name="attributes"></param>
        /// <returns></returns>
        public GtpType SetGenericParameterAttributes(GenericParameterAttributes attributes)
        {
            _parameterAttributes = attributes;
            return this;
        }
    }

TypeBuilder 提供了一個方法 DefineGenericParameters 用於定義泛型類型參數。GtpTypeInitialize 方法是將基類約束、介面約束等設定到 GenericTypeParameterBuilder 對象里。如下:

        /// <summary>
        /// 定義泛型參數。
        /// </summary>
        /// <param name="parameterTypes"></param>
        /// <returns></returns>
        public void DefineGenericParameters(params GtpType[] parameterTypes)
        {
            if (_genericParameterTypes != null)
            {
                throw new InvalidOperationException("已經定義過泛型參數。");
            }

            _genericParameterTypes = parameterTypes.ToDictionary(s => s.Name);
            var builders = _typeBuilder.DefineGenericParameters(parameterTypes.Select(s => s.Name).ToArray());

            for (var i = 0; i < parameterTypes.Length; i++)
            {
                parameterTypes[i].Initialize(builders[i]);
            }
        }

  然後,DynamicTypeBuilder 提供了定義方法、構造函數、屬性和嵌套類型的方法,將工作交給這些不同的構造器。如下:

        /// <summary>
        /// 定義一個屬性。
        /// </summary>
        /// <param name="propertyName">屬性的名稱。</param>
        /// <param name="propertyType">屬性的類型。</param>
        /// <param name="accessibility">指定屬性的可見性。</param>
        /// <param name="modifier">指定屬性的調用屬性。</param>
        /// <returns>新的 <see cref="DynamicPropertyBuilder"/>。</returns>
        public virtual DynamicPropertyBuilder DefineProperty(string propertyName, Type propertyType, Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard)
        {
            return new DynamicPropertyBuilder(Context, propertyName, propertyType, accessibility, modifier);
        }

        /// <summary>
        /// 定義一個方法。
        /// </summary>
        /// <param name="methodName">方法的名稱。</param>
        /// <param name="returnType">返回值的類型,如果為 void 則該參數為 null。</param>
        /// <param name="parameterTypes">一個數組,表示方法的傳入參數類型。</param>
        /// <param name="accessibility">指定方法的可見性。</param>
        /// <param name="modifier">指定方法的調用屬性。</param>
        /// <param name="ilCoding">方法體的 IL 過程。</param>
        /// <returns>新的 <see cref="DynamicMethodBuilder"/>。</returns>
        public virtual DynamicMethodBuilder DefineMethod(string methodName, Type? returnType = null, Type[]? parameterTypes = null, Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, Action<BuildContext> ilCoding = null)
        {
            return new DynamicMethodBuilder(Context, methodName, returnType, parameterTypes, accessibility, modifier, ilCoding);
        }

        /// <summary>
        /// 定義一個構造函數。
        /// </summary>
        /// <param name="parameterTypes"></param>
        /// <param name="accessibility"></param>
        /// <param name="modifier"></param>
        /// <param name="ilCoding"></param>
        /// <returns></returns>
        public virtual DynamicConstructorBuilder DefineConstructor(Type[] parameterTypes, Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, Action<BuildContext> ilCoding = null)
        {
            return new DynamicConstructorBuilder(Context, parameterTypes, accessibility, modifier, ilCoding);
        }

        /// <summary>
        /// 定義一個欄位。
        /// </summary>
        /// <param name="fieldName">欄位的名稱。</param>
        /// <param name="fieldType">欄位的類型。</param>
        /// <param name="defaultValue">預設值。</param>
        /// <param name="accessibility"></param>
        /// <param name="modifier"></param>
        /// <returns></returns>
        public virtual DynamicFieldBuilder DefineField(string fieldName, Type fieldType, object? defaultValue = null, Accessibility accessibility = Accessibility.Private, Modifier modifier = Modifier.Standard)
        {
            return new DynamicFieldBuilder(Context, fieldName, fieldType, defaultValue, accessibility, modifier);
        }

        /// <summary>
        /// 定義一個嵌套的類型。
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="accessibility"></param>
        /// <param name="baseType"></param>
        /// <returns></returns>
        public virtual DynamicTypeBuilder DefineNestedType(string typeName, Accessibility accessibility = Accessibility.Private, Type? baseType = null)
        {
            var nestedType = new DynamicTypeBuilder(Context, typeName, accessibility, baseType);
            _nestedTypeBuilders.Add(nestedType);
            return nestedType;
        }

        /// <summary>
        /// 使用當前的構造器定義一個動態介面。
        /// </summary>
        /// <param name="typeName">類型的名稱。</param>
        /// <param name="accessibility">指定類的可見性。</param>
        /// <returns></returns>
        public DynamicInterfaceBuilder DefineNestedInterface(string typeName, Accessibility accessibility = Accessibility.Public)
        {
            var typeBuilder = new DynamicInterfaceBuilder(Context, typeName, accessibility);
            _nestedTypeBuilders.Add(typeBuilder);
            return typeBuilder;
        }

        /// <summary>
        /// 使用當前構造器定義一個枚舉。
        /// </summary>
        /// <param name="enumName">枚舉的名稱。</param>
        /// <param name="underlyingType">枚舉的類型。</param>
        /// <param name="accessibility">指定枚舉的可見性。</param>
        /// <returns></returns>
        public DynamicEnumBuilder DefineNestedEnum(string enumName, Type? underlyingType = null, Accessibility accessibility = Accessibility.Public)
        {
            var enumBuilder = new DynamicEnumBuilder(Context, enumName, underlyingType ?? typeof(int), accessibility);
            _nestedTypeBuilders.Add(enumBuilder);
            return enumBuilder;
        }

DynamicMethodBuilder

  動態方法構造器在定義時,需要指定參數類型,返回類型等等,對於泛型方法,也需要使用 GtpType 類型來定義。以下的方法是用於處理泛型方法的:

        private void ProcessGenericMethod()
        {
            Dictionary<string, GenericTypeParameterBuilder>? builders = null;

            //方法參數里有泛型類型參數
            if (ParameterTypes?.Any(s => s is GtpType) == true)
            {
                //篩選沒有在類型構造器里定義過的泛型類型參數
                var names = ParameterTypes.Where(s => s is GtpType).Where(s => !Context.TypeBuilder.TryGetGenericParameterType(s.Name, out _)).Cast<GtpType>().Select(s => s.Name).ToArray();

                //如果有新的泛型類型參數,則在方法構造器里定義
                if (names.Length > 0)
                {
                     builders = _methodBuilder.DefineGenericParameters(names).ToDictionary(s => s.Name);
                }

                for (var i = 0; i < ParameterTypes.Length; i++)
                {
                    if (ParameterTypes[i] is GtpType gt)
                    {
                        if (builders?.TryGetValue(gt.Name, out var parb) == true)
                        {
                            ParameterTypes[i] = gt.Initialize(parb);
                        }
                        else if (Context.TypeBuilder.TryGetGenericParameterType(gt.Name, out var gt1))
                        {
                            ParameterTypes[i] = gt1.GenericTypeParameterBuilder;
                        }
                    }
                }

                MethodBuilder.SetParameters(ParameterTypes);
            }

            //如果返回類型是泛型類型
            if (ReturnType is GtpType rgt)
            {
                //先在方法構造器里查找
                if (builders?.TryGetValue(rgt.Name, out var retb) == true)
                {
                    ReturnType = rgt.Initialize(retb);
                }
                //在類型構造器里查找
                else if (Context.TypeBuilder.TryGetGenericParameterType(rgt.Name, out var gt1))
                {
                    ReturnType = gt1.GenericTypeParameterBuilder;
                }
            }
        }

  這樣,就完美處理了泛型方法,文章最後會例舉測試用例進一步加深印象。
  如果動態類型指定了所繼承的基類,還需要處理重載方法,以下的方法用於在父類中查找與名稱和參數相匹配的方法:

        private MethodInfo? FindMethod(string methodName, IEnumerable<Type> parameterTypes)
        {
            MethodInfo? method = null;
            if (Context.TypeBuilder.BaseType != null)
            {
                method = Helper.MatchMethod(Context.TypeBuilder.BaseType, methodName, parameterTypes);

                if (method != null && !method.IsVirtual)
                {
                    throw new DynamicBuildException("所定義的方法在父類中未標記 virtual、abstract 或 override。");
                }
            }

            //在實現的介面中查找方法
            var interfaceTypes = Context.TypeBuilder.InterfaceTypes
                .Union(Context.TypeBuilder.InterfaceTypes.SelectMany(s => s.GetInterfaces()))
                .Distinct().ToList();

            //在實現介面中去查找方法
            if (method == null && interfaceTypes.Count != 0)
            {
                foreach (var type in interfaceTypes)
                {
                    method = type.GetMethod(methodName, parameterTypes == null ? Type.EmptyTypes : parameterTypes.ToArray());
                    if (method != null)
                    {
                        break;
                    }
                }
            }

            return method;
        }

  在處理 MethodAttributes 時,如果匹配到父類的方法,則也會有不同的處理,這些屬性的含義,需要自己去慢慢理解。如下:

        private MethodAttributes GetMethodAttributes(string methodName, IEnumerable<Type> parameterTypes, Accessibility accessibility, Modifier modifier)
        {
            var method = FindMethod(methodName, parameterTypes);
            var isOverride = method != null && method.IsVirtual;
            var isInterface = isOverride && method!.DeclaringType!.IsInterface;
            var isBaseType = isOverride && method!.DeclaringType == Context.TypeBuilder.BaseType;
            if (method != null)
            {
                Context.BaseMethod = method;
            }

            var attrs = GetMethodAttributes(accessibility, modifier);
            if (isOverride)
            {
                attrs |= MethodAttributes.Virtual;

                //去掉 NewSlot
                if (isBaseType && _attributes.HasFlag(MethodAttributes.NewSlot))
                {
                    attrs &= ~MethodAttributes.NewSlot;
                }
                else if (isInterface)
                {
                    //如果沒有傳入 modifier,則加 Final 去除上面定義的 Virtual
                    if (modifier == Modifier.Standard)
                    {
                        attrs |= MethodAttributes.Final;
                    }

                    attrs |= MethodAttributes.NewSlot;
                }
            }
            else if (method != null)
            {
            }

            return attrs;
        }

  在 DefineMethod 方法中,最後一個參數 ilCoding 允許你用 IL 指令編寫一段代碼,以作為方法體。Emitter 屬性是一個 EmitHelper 對象,它是對 ILGenerator 對象的包裝,可以更方便地使用鏈式語法編寫 IL 指令。從構造函數里調用 InitBuilder 方法可以看出它的工作原理:

        private void InitBuilder()
        {
            //此處略去
            Context.Emitter = new EmitHelper(_methodBuilder.GetILGenerator(), _methodBuilder);
            //此處略去
            if (_buildAction != null)
            {
                _buildAction(Context);
            }
            else
            {
                Context.Emitter.ret();
            }
        }

  通過 DefineMethod 方法編寫 IL 指令後,還可以使用 OverwriteCode 方法進行覆蓋,或使用 AppendCode 方法進行追加,如下:

        /// <summary>
        /// 追加新的 MSIL 代碼到構造器中。
        /// </summary>
        /// <param name="ilCoding"></param>
        /// <returns></returns>
        public DynamicMethodBuilder AppendCode(Action<EmitHelper> ilCoding)
        {
            ilCoding?.Invoke(Context.Emitter);

            return this;
        }

        /// <summary>
        /// 使用新的 MSIL 代碼覆蓋構造器中的現有代碼。
        /// </summary>
        /// <param name="ilCoding"></param>
        /// <returns></returns>
        public DynamicMethodBuilder OverwriteCode(Action<EmitHelper> ilCoding)
        {
            var field = typeof(MethodBuilder).GetField("m_ilGenerator", BindingFlags.NonPublic | BindingFlags.Instance);
            if (field != null)
            {
                var cons = typeof(ILGenerator).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
                field.SetValue(_methodBuilder, cons.Invoke(new[] { _methodBuilder }));
                Context.Emitter = new EmitHelper(_methodBuilder.GetILGenerator(), _methodBuilder);
            }

            return AppendCode(ilCoding);
        }

DynamicPropertyBuilder

  動態屬性構造器就要容易一些,只需要用它來定義 get 和 set 方法,它一般是自動屬性的,如果要使用到欄位域,則傳入欄位域構造器即可。如果你的 get 或 set 方法很複雜,那就使用 ilCoding 進行指令編碼。如下:

        /// <summary>
        /// 獲取當前的 <see cref="DynamicFieldBuilder"/>。
        /// </summary>
        /// <returns></returns>
        public DynamicFieldBuilder FieldBuilder
        {
            get
            {
                return _fieldBuilder ?? (_fieldBuilder = Context.TypeBuilder.DefineField(string.Format("m_<{0}>", Name), PropertyType));
            }
        }

        /// <summary>
        /// 定義屬性的 Get 訪問方法。
        /// </summary>
        /// <param name="accessibility">指定方法的可見性。</param>
        /// <param name="modifier">指定方法的調用屬性。</param>
        /// <param name="ilCoding">方法體的 IL 過程。</param>
        /// <returns>新的 <see cref="DynamicMethodBuilder"/>。</returns>
        public DynamicMethodBuilder DefineGetMethod(Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, Action<BuildContext> ilCoding = null)
        {
            var isInterface = Context.TypeBuilder is DynamicInterfaceBuilder;
            var method = new DynamicMethodBuilder(Context, string.Concat("get_", GetMethodName()), PropertyType, Type.EmptyTypes, accessibility, modifier, ctx =>
                {
                    if (isInterface)
                    {
                        return;
                    }

                    if (ilCoding != null)
                    {
                        ilCoding(ctx);
                    }
                    else
                    {
                        ctx.Emitter.ldarg_0.ldfld(FieldBuilder.FieldBuilder).ret();
                    }
                });
            PropertyBuilder.SetGetMethod(method.MethodBuilder);
            return method;
        }

        /// <summary>
        /// 定義屬性的 Get 訪問方法。
        /// </summary>
        /// <param name="accessibility">指定方法的可見性。</param>
        /// <param name="modifier">指定方法的調用屬性。</param>
        /// <param name="fieldBuilder">指定一個屬性相關的 <see cref="DynamicFieldBuilder"/>。</param>
        /// <returns>新的 <see cref="DynamicMethodBuilder"/>。</returns>
        public DynamicMethodBuilder DefineGetMethodByField(Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, DynamicFieldBuilder? fieldBuilder = null)
        {
            var isInterface = Context.TypeBuilder is DynamicInterfaceBuilder;
            var method = new DynamicMethodBuilder(Context, string.Concat("get_", GetMethodName()), PropertyType, Type.EmptyTypes, accessibility, modifier, ctx =>
                {
                    if (isInterface)
                    {
                        return;
                    }

                    fieldBuilder ??= FieldBuilder;

                    ctx.Emitter.ldarg_0.ldfld(fieldBuilder.FieldBuilder).ret();
                });

            PropertyBuilder.SetGetMethod(method.MethodBuilder);
            return method;
        }

DynamicConstructorBuilder

動態構造函數構造器,如果類型繼承了基類,則預設的方法體需要調用父類構造函數。如下:

        internal DynamicConstructorBuilder(BuildContext context, Type[] parameterTypes, Accessibility accessibility = Accessibility.Public, Modifier modifier = Modifier.Standard, Action<BuildContext> ilCoding = null)
            : base(accessibility, modifier)
        {
            Context = new BuildContext(context) { ConstructorBuilder = this };
            ParameterTypes = parameterTypes;

            if (ilCoding == null)
            {
                if (context.TypeBuilder.BaseType != typeof(object))
                {
                    var constructor = Helper.MatchConstructor(Context.TypeBuilder.BaseType, parameterTypes);

                    if (constructor != null)
                    {
                        ilCoding = c => c.Emitter.ldarg_0
                            .If(parameterTypes != null, b => b.For(0, parameterTypes!.Length, (e, i) => e.ldarg(i + 1)))
                            .call(constructor).ret();
                    }
                }
            }

            ilCoding ??= c => c.Emitter.ret();

            _buildAction = ilCoding;
            _attributes = GetMethodAttributes(accessibility, modifier);
            InitBuilder();
        }

  最後,說一下關於自定義特性。在構造器基類 DynamicBuilder 里,有一個 SetCustomAttribute 方法,它可以使用 lambda 表達式來指定自定義特性。

測試用例

  創建一個類型,繼承基類,實現介面:

        [TestMethod]
        public void TestTypeBuilder()
        {
            /*
            public class MyClass : MyBaseClass, IMyInterface
            {
                public string Title { get; set; }
                public void HelloWorld()
                {
                }
                public void WriteName(string a1, string a2)
                {
                }
            }
            */

            var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
            var typeBuilder = assemblyBuilder.DefineType("MyClass");

            typeBuilder.BaseType = typeof(MyBaseClass);

            typeBuilder.ImplementInterface(typeof(IMyInterface));

            var methodBuilder = typeBuilder.DefineMethod("HelloWorld");
            var propertyBuilder = typeBuilder.DefineProperty("Title", typeof(string)).DefineGetSetMethods();

            methodBuilder = typeBuilder.DefineMethod("WriteName", typeof(string), new[] { typeof(string) });

            var type = typeBuilder.CreateType();

            Assert.IsTrue(typeof(IMyInterface).IsAssignableFrom(type));
        }

  創建一個泛型類型,以及泛型方法:

        [TestMethod]
        public void TestDefineGenericType()
        {
            /*
            public class MyClass<T, TS> where T : MyBaseClass
            {
                public MyClass(TS ts)
                {
                }
                public T Hello<TV>(T t, TV tv)
                {
                    Console.WriteLine(tv);
                    return t;
                }
            }
            */

            var gt = new GtpType("T").SetBaseTypeConstraint(typeof(MyBaseClass));

            var assemblyBuilder = new DynamicAssemblyBuilder("MyAssembly");
            var typeBuilder = assemblyBuilder.DefineType("MyClass");
            //定義泛型類型參數
            typeBuilder.DefineGenericParameters(gt, new GtpType("TS"));

            //定義構造函數
            typeBuilder.DefineConstructor(new Type[] { new GtpType("TS") });

            //定義一個泛型方法,TV不在類中定義,所以屬於方法的泛型類型參數
            var methodBuilder = typeBuilder.DefineMethod("Hello", gt, new Type[] { gt, new GtpType("TV") }, ilCoding: c =>
            {
                c.Emitter
                .ldarg_2.call(typeof(Console).GetMethod("WriteLine", new[] { typeof(object) }))
                .ldarg_1.ret();
            });

            var type = typeBuilder.CreateType().MakeGenericType(typeof(MyBaseClass), typeof(int));
            var obj = Activator.CreateInstance(type, 100);

            var method = type.GetMethod("Hello").MakeGenericMethod(typeof(string));
            var value = method.Invoke(obj, new object[] { new MyBaseClass(), "world" });

            Assert.IsInstanceOfType(value, typeof(MyBaseClass));
        }

  定義泛型方法:

        /// <summary>
        /// 使用泛型參數測試DefineMethod方法。
        /// </summary>
        [TestMethod()]
        public void TestDefineGenericMethod()
        {
            var typeBuilder = CreateBuilder();

            /*
            public class testClass
            {
                public void Hello<T1, T2>(string name, T1 any1, T2 any2)
                {
                    Console.Write(name + any1 + any2);
                }
            }
            */
            var methodBuilder = typeBuilder.DefineMethod("Hello", parameterTypes: new Type[] { typeof(string), new GtpType("T1"), new GtpType("T2") });
            methodBuilder.DefineParameter("name");
            methodBuilder.DefineParameter("any1");
            methodBuilder.DefineParameter("any2");

            var paraCount = methodBuilder.ParameterTypes.Length;

            methodBuilder.OverwriteCode(e =>
            {
                e.ldc_i4(paraCount)
                .newarr(typeof(object))
                .dup.ldc_i4_0.ldarg_1.stelem_ref
                .For(1, paraCount, (e1, i) =>
                {
                    e1.dup.ldc_i4(i).ldarg(i + 1).box(methodBuilder.ParameterTypes[i]).stelem_ref.end();
                })
                .call(typeof(string).GetMethod("Concat", new[] { typeof(object[]) }))
                .call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))
                .ret();
            });

            var type = typeBuilder.CreateType();

            var method = type.GetMethod("Hello");
            Assert.IsNotNull(method);
            Assert.IsTrue(method.IsGenericMethod);

            var obj = Activator.CreateInstance(type);

            method = method.MakeGenericMethod(typeof(int), typeof(decimal));

            method.Invoke(obj, new object[] { "fireasy", 22, 45m });
        }

  顯式實現方法:

        /// <summary>
        /// 使用介面成員顯式實現測試ImplementInterface方法。
        /// </summary>
        [TestMethod()]
        public void ImplementInterfaceWithExplicitMember()
        {
            /*
            public class testClass : IDynamicMethodInterface
            {
                void IDynamicMethodInterface.Test(int s)
                {
                    Console.WriteLine(s);
                }
            }
            */
            var typeBuilder = CreateBuilder();

            typeBuilder.ImplementInterface(typeof(IDynamicMethodInterface));
            var methodBuilder = typeBuilder.DefineMethod("Test",
                parameterTypes: new[] { typeof(int) },
                modifier: Modifier.ExplicitImpl,
                ilCoding: (e) => e.Emitter.ldstr("fireasy").call(typeof(Console).GetMethod("WriteLine", new[] { typeof(string) })).ret());

            methodBuilder.DefineParameter("s");

            var type = typeBuilder.CreateType();

            var obj = Activator.CreateInstance(type) as IDynamicMethodInterface;
            obj.Test(111);

            Assert.IsTrue(typeof(IDynamicMethodInterface).IsAssignableFrom(type));
        }

  繼承泛型基類,並且定義構造函數:

        /// <summary>
        /// 測試DefineConstructor方法。
        /// </summary>
        [TestMethod()]
        public void TestDefineConstructorForGeneric()
        {
            var typeBuilder = CreateBuilder();

            /*
            public class testClass<T> : GenericClass<T>
            {
                public testClass(T value)
                    : base (value)
                {
                }
            }
            */

            var gtp = new GtpType("T");
            typeBuilder.BaseType = typeof(GenericClass<>);
            typeBuilder.DefineGenericParameters(gtp);

            var constructorBuilder = typeBuilder.DefineConstructor(new Type[] { gtp });
            constructorBuilder.DefineParameter("value");

            var type = typeBuilder.CreateType();

            type = type.MakeGenericType(typeof(string));

            var obj = Activator.CreateInstance(type, new[] { "fireasy" });
            Assert.IsNotNull(obj);

        }

  最後,奉上 Fireasy 3 的開源地址:https://gitee.com/faib920/fireasy3 ,歡迎大家前來捧場。

  本文相關代碼請參考:
  https://gitee.com/faib920/fireasy3/src/libraries/Fireasy.Common/Emit
  https://gitee.com/faib920/fireasy3/tests/Fireasy.Common.Tests/EmitTests.cs

  更多內容請移步官網 http://www.fireasy.cn 。

作者:fireasy
出處:http://fireasy.cnblogs.com
官網:http://www.fireasy.cn
版權聲明:本文的版權歸作者與博客園共有。轉載時須註明本文的詳細鏈接,否則作者將保留追究其法律責任。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Tomcat 是 Apache 軟體基金會(Apache Software Foundation)的一個開源項目,實現了 Servlet 及 JSP 規範,可以用來部署 WEB 應用及 WebService;本文主要介紹其基本概念。 1、Tomcat 安裝 安裝 Tomcat 之前需要先安裝 Jav ...
  • 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> OpenGL ES 特效 零基礎 OpenGL ES 學習路線推薦 : OpenGL ES 學習目錄 >> O ...
  • java 線程池預設提供了幾種拒絕策略: 這幾個策略都實現了RejectedExecutionHandler,拿DiscardOldestPolicy來說,查看源碼: 核心代碼只有2行: e.getQueue().poll() 從列表裡彈出1個(最早的)任務,以便讓隊列空出1個位置 e.execut ...
  • 什麼是Git? Git是一個版本控制系統,用於跟蹤電腦文件的變化。Git是一個跟蹤電腦文件變化的版本控制系統,用於幫助協調一個項目中幾個人的工作,同時跟蹤一段時間的進展。換句話說,我們可以說它是一個促進軟體開發中源代碼管理的工具。 Git和SVN的區別 Git是分散式版本控制系統,SVN是集中式 ...
  • 廣發基金外包面試題 說說Spring AOP 說說stream流的常用API 線程池的參數 MCVV,什麼是普通讀和快照讀 redis分片模式是什麼,redis事務,分片模式下事務會生效嗎 說說rocket mq分散式事務是怎麼做的 為什麼要劃分session,怎麼解決耦合問題 說說是怎麼解決分散式 ...
  • 最近接到一個新的需求,需要上傳2G左右的視頻文件,用測試環境的OSS試了一下,上傳需要十幾分鐘,再考慮到公司的資源問題,果斷放棄該方案。 一提到大文件上傳,我最先想到的就是各種網盤了,現在大家都喜歡將自己收藏的「小電影」上傳到網盤進行保存。網盤一般都支持斷點續傳和文件秒傳功能,減少了網路波動和網路... ...
  • 1. 垃圾回收器 1.1. 對象可以在被需要時創建,不再使用時由JVM自動回收 1.2. GC是查找不再使用的對象,然後回收這些對象相關記憶體的過程 1.2.1. 找到不使用的對象、回收其記憶體、壓縮堆記憶體 1.3. 優化垃圾回收器比跟蹤指針引起的bug要容易得多(且耗時更少) 1.4. VM必須定期搜 ...
  • 簡介 Dapper是介於Entity framework與ADO的折中選擇。既滿足手寫查詢的高性能需求,又簡化了資料庫對象映射為記憶體對象的繁雜工作。Dapper.Contrib是對Dapper的進一步封裝,使對象的基本增刪改查等操作進一步簡化。 為什麼使用Dapper.Contrib 如果僅僅使用D ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...