一. 前言
在《轻量级AOP框架-移植python的装饰器(Decorator)到C#(思考篇)》中,文章分析了Python中Decorator的原理以及C#移植的可行性,在本篇中,文章将继续探讨如何将这个想法实实在在的表现出来,因此本篇的目标是:一个初级但是可用的Decorator实现。
如果您对本文的基本思路存在疑惑,请先阅读思考篇。
二. 实现分析
上篇中,我们考虑实现一个Wrapper类来做到模仿Python的函数替换功能,然而,在实际使用中,如果靠人工书写,很显然是一个不切实际的想法,因此,框架的关键在于对被装饰方法的处理,当前,我们一般使用动态代理或者静态织入的方式进行该操作,然而,无论是哪种方法,关键点都在于对现有代码的“动态修改”(动态代理的修改在于运行时,静态织入的修改在于编译时)。
在本篇中,我们考虑一个动态代理的实现,具体的运作方式如下:
- 运行时采用框架中的工厂生成代理对象,即:调用框架中的工厂方法,传入欲生成对象类型。因此对象创建方式将发生改变:默认情况下,我们可能采用var testClass = new TestClass();的方式生成对象,在使用代理的情况下,必须强制使用var testClass = xxx.CreateInstanse<TestClass>();的方式生成对象。
- 框架工厂类获取到对象类型之后,检查对象是否为可继承对象,如果不是,则无法生成代理类,否则,进行下一步。
- 调用动态类生产引擎,生成TestClassWrapper类,并从TestClass继承。
- 采用一定的方式,重写TestClass中欲进行处理的方法,以满足上一篇中预设的结果
- 生成TestClassWrapper类实例并返回
三. 编码难点
在了解了具体的运作方式之后,我们可以分别考虑各个步骤的实现难点,第一和第二都不难,使用基本的反射即可实现,主要的问题在于3-5步,下面我们分别对这几步的实现进行编码难点分析。
对于第三步的类继承,很显然,这首先要具备一个条件,那就是原始类是可继承的,否则,也无从谈起TestClassWrapper的生成,如果满足条件,那么可以使用反射创建动态类。同时,在c#中,我们需要创建一个动态程序集来容纳这些动态类。
对于第四步,系统需要重写欲处理的方法,要达到这个目标,我们只能请出我们的终极大神MSIL了,在C#中,可以使用Emit的方式进行IL嵌入编程,虽然麻烦了点,但这总算也能高效的解决问题。
对于第五步,一般来说,可以调用Activator.CreateInstance方法来创建对象,然而,在本人的另一篇博客《探究.net对象的创建,质疑《再谈Activator.CreateInstance(Type type)方法创建对象和Expression Tree创建对象性能的比较》中,我们可以了解到Activator.CreateInstance并不是最高效的做法,因此,框架将考虑使用更高效的方式,如Emit或者ExpressionTree的方式生成委托并构造对象。
四. 具体编码
在编码前,为了方便快速的构造框架,个人将一些成熟的代码放入框架中,以提高效率,这部分代码主要有:FastReflection,可以通过Emit快速构造方法调用委托,CodeGenerator,Nbear框架中的一个IL编程帮助类,通过它可以让我们简化不少IL操作。
现在万事俱备,我们开始一步步的创造框架吧,首先定义DecoratorFilter接口,以方便扩展Attribute,同时,为了方便,我们也定义一个DecoratorContexe来取代上篇中的直接传入Wrapper方法的委托对象,DecoratorFilter接口定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public interface IDecoratorFilter { Func< object , object [], object > Execute(DecoratorContext context); } 其中的DecoratorContext类包含了欲封装方法的基本属性,其定义如下: public sealed class DecoratorContext { /// <summary> /// 该方法通用调用委托 /// </summary> public Func< object , object [], object > Invoker { get ; private set ; } /// <summary> /// this对象 /// </summary> public object Instanse { get ; private set ; } /// <summary> /// 方法参数列表 /// </summary> public object [] Parameters { get ; private set ; } public DecoratorContext(Func< object , object [], object > invoker, object instanse, object [] parameters) { Invoker = invoker; Instanse = instanse; Parameters = parameters; } } |
然后需要完成的是DynamicTypeBuilder类,该类负责动态程序集,动态类型,动态方法的创建,具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | internal class DynamicTypeBuilder { //为动态程序集生成名称 private readonly string assemblyName = Guid.NewGuid().ToString(); //程序集主模块 private readonly string mainModuleName = "Main" ; public AssemblyBuilder AssemblyBuilder { get ; private set ; } public ModuleBuilder MainModuleBuilder { get ; private set ; } public DynamicTypeBuilder() { AssemblyName an = new AssemblyName(assemblyName); //构造一个可运行的动态程序集对象 AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); //为该程序集构造主模块 MainModuleBuilder = AssemblyBuilder.DefineDynamicModule(mainModuleName); } /// <summary> /// 从Type类派生一个新的代理类 /// </summary> /// <param name="type">基类</param> /// <returns></returns> public TypeBuilder CreateTypeBuilder(Type type) { TypeBuilder typeBuilder = MainModuleBuilder.DefineType(type.Name + "_" + Guid.NewGuid().ToString(), TypeAttributes.Class | TypeAttributes.Public, type); return typeBuilder; } /// <summary> /// 根据给定的方法对象和类型对象构造CodeGenerator /// </summary> /// <param name="typeBuilder"></param> /// <param name="method"></param> /// <returns></returns> public CodeGenerator CreateMethodCodeGenerator(TypeBuilder typeBuilder, MethodInfo method) { CodeGenerator cg = new CodeGenerator(typeBuilder, method.Name, MethodAttributes.Virtual | MethodAttributes.Public | MethodAttributes.ReuseSlot | MethodAttributes.HideBySig, CallingConventions.HasThis, method.ReturnType, method.GetParameters().Select(e => e.ParameterType).ToArray()); typeBuilder.DefineMethodOverride(cg.CurrentMethod, method); return cg; } } |
为了提高框架的运行效率,系统也构造一个简单的缓存系统,将缓存一些元数据,详细情况可以参考完整源文件。
最后考虑最麻烦的TypeFactory类,该类中,最重要的方法就是CreateType,它可以使说框架中最核心的部分了,通过该该方法,我们创建了一个新的Class,该Class中完成了Decorator的核心实现,下面给出该方法的实现,该类的完整实现也请参考源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | public static Type CreateType(Type rawType) { var typeBuilder = builder.CreateTypeBuilder(rawType); MetaCacheItem metaCacheItem = MetaCache.Get(rawType); MethodInfo getTypeMethodInfo = rawType.GetMethod( "GetType" ); foreach ( var item in metaCacheItem.Methods) { if (item.Method.IsVirtual && item.Filters != null && item.Filters.Length > 0) { var cg = builder.CreateMethodCodeGenerator(typeBuilder, item.Method); var parameters = item.Method.GetParameters(); var cmpLabel = cg.DefineLabel(); var loopLabel = cg.DefineLabel(); //Type type; var typeLocal = cg.DeclareLocal(typeType); //MethodInfo thisMethodInfo; var thisMethodInfoLocal = cg.DeclareLocal(methodInfoType); //Type[] typeParameters; var typeParametersLocal = cg.DeclareLocal(typeArrayType); //MetaCacheItem metaCacheItem; var metaCacheItemLocal = cg.DeclareLocal(metaCacheItemType); //MetaMethodInfo metaMethodInfo; var metaMethodInfoLocal = cg.DeclareLocal(metaMethodInfoType); //Func<object, object[], object> fun; var funLocal = cg.DeclareLocal(genericInvokerType); //object[] parameters; var parametersLocal = cg.DeclareLocal(objectArrayType); //IDecoratorFilter[] filters; var filtersLocal = cg.DeclareLocal(iDecoratorFilterArrayType); //IDecoratorFilter item; var itemLocal = cg.DeclareLocal(iDecoratorFilterType); //int num; var numLocal = cg.DeclareLocal(int32Type); //type = this.GetType(); //cg.Ldarg(0); //cg.Call(getTypeMethodInfo); cg.Ldtoken(rawType); cg.Call(getTypeFromHandleMethodInfo); cg.Stloc(typeLocal); //typeParameters = new Type[] { xxx }; cg.NewArray(typeType, parameters.Length); cg.Stloc(typeParametersLocal); for ( int i = 0; i < parameters.Length; i++) { cg.Ldloc(typeParametersLocal); cg.Ldc(i); cg.Ldtoken(parameters[i].ParameterType); cg.Call(getTypeFromHandleMethodInfo); //cg.Ldarg(i + 1); //cg.Call(getTypeMethodInfo); cg.Stelem(typeType); } //thisMethodInfo = type.GetMethod("xxx", typeParameters); cg.Ldloc(typeLocal); cg.Ldstr(item.Method.Name); cg.Ldloc(typeParametersLocal); cg.Call(getMethodMethodInfo); cg.Stloc(thisMethodInfoLocal); //metaCacheItem = MetaCache.Get(type); cg.Ldloc(typeLocal); cg.Call(metaCacheGetMethodInfo); cg.Stloc(metaCacheItemLocal); //metaMethodInfo = TypeFactory.FindMetaMethodInfo(metaCacheItem.Methods, thisMethod); cg.Ldloc(metaCacheItemLocal); cg.Call(metaCacheItemMethodsGetMethodInfo); cg.Ldloc(thisMethodInfoLocal); cg.Call(findMetaMethodInfoMethodInfo); cg.Stloc(metaMethodInfoLocal); //fun = TypeFactory.CreateGenericInvoker(metaMethodInfo.Method); cg.Ldloc(metaMethodInfoLocal); cg.Call(metaMethodInfoMethodGetMethodInfo); cg.Call(createGenericInvokerMethodInfo); cg.Stloc(funLocal); //parameters = new object[] { xxx }; cg.NewArray(objectType, parameters.Length); cg.Stloc(parametersLocal); for ( int i = 0; i < parameters.Length; i++) { cg.Ldloc(parametersLocal); cg.Ldc(i); cg.Ldarg(i + 1); if (parameters[i].ParameterType.IsValueType) { cg.Box(parameters[i].ParameterType); } cg.Stelem(typeType); } cg.Ldloc(metaMethodInfoLocal); cg.Call(metaMethodInfoFiltersGetMethodInfo); cg.Stloc(filtersLocal); //开始循环 cg.Ldc(0); cg.Stloc(numLocal); cg.Br(cmpLabel); cg.MarkLabel(loopLabel); cg.Ldloc(filtersLocal); cg.Ldloc(numLocal); cg.Ldelem(iDecoratorFilterType); cg.Stloc(itemLocal); //loop //item.Execute(new DecoratorContext(fun, this, parameters)); cg.Ldloc(itemLocal); cg.Ldloc(funLocal); cg.Ldarg(0); cg.Ldloc(parametersLocal); cg.New(decoratorContextConstructorInfo); cg.Call(iDecoratorFilterExecuteMethodInfo); cg.Stloc(funLocal); //endloop cg.Ldloc(numLocal); cg.Ldc(1); cg.Add(); cg.Stloc(numLocal); cg.MarkLabel(cmpLabel); cg.Ldloc(numLocal); cg.Ldloc(filtersLocal); cg.Ldlen(); cg.Blt(loopLabel); //return (string)fun(this, parameters) cg.Ldloc(funLocal); cg.Ldarg(0); cg.Ldloc(parametersLocal); cg.Call(genericInvokeInvokerMethodInfo); if (item.Method.ReturnType != voidType && item.Method.ReturnType != objectType) { cg.ConvertValue(objectType, item.Method.ReturnType); } else if (item.Method.ReturnType == voidType) { cg.Pop(); } cg.Ret(); } } return typeBuilder.CreateType(); } |
将这部分IL翻译一下,如果我们定义了一个方法public virtual string Test(string p),那么自动生成的方法大致会像下面一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public override string Test( string p) { Type type = typeof (TestClass); MethodInfo thisMethod = type.GetMethod( "Test" , new Type[] { typeof ( string ) }); MetaCacheItem metaCacheItem = MetaCache.Get(type); MetaMethodInfo thisMetaMethod = metaCacheItem.Methods.FirstOrDefault(e => e.Method == thisMethod); Func< object , object [], object > fun = TypeFactory.CreateGenericInvoker(thisMetaMethod.Method); object [] parameters = new object [] { p }; foreach ( var item in thisMetaMethod.Filters) { DecoratorContext context = new DecoratorContext(fun, this , parameters); fun = item.Execute(context); } return ( string )fun( this , parameters); } |
五. 框架测试
首先测试功能,我们仍然以上篇中第一个Python代码为例,想办法达到一样的效果,准备的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /// <summary> /// 和@logger功能一致的LoggerAttribute实现 /// </summary> public class LoggerAttribute : Attribute, IDecoratorFilter { public string Name { get ; private set ; } public LoggerAttribute( string name) { Name = name; } #region IDecoratorFilter Members public Func< object , object [], object > Execute(DecoratorContext context) { return (instanse, parameters) => { Console.WriteLine( "User is {0}." , Name); Console.WriteLine( "Start Logging." ); var result = context.Invoker(instanse, parameters); Console.WriteLine( "End Logging." ); return result; }; } #endregion } /// <summary> /// 和@debuger功能一致的DebuggerAttribute实现 /// </summary> public class DebuggerAttribute : Attribute, IDecoratorFilter { public string Name { get ; private set ; } public DebuggerAttribute( string name) { Name = name; } #region IDecoratorFilter Members public Func< object , object [], object > Execute(DecoratorContext context) { return (instanse, parameters) => { Console.WriteLine( "Debug {0}" , Name); Console.WriteLine( "Start Debug." ); var result = context.Invoker(instanse, parameters); Console.WriteLine( "End Debug." ); return result; }; } #endregion } public class TestClass { [Logger( "Leven" )] [Debugger( "test" )] public virtual string Test() { Console.WriteLine( "Method TestClass::Test() called." ); return "I am Result." ; } } |
测试代码很简单:
1 2 | var instanse = TypeFactory.CreateInstanse<TestClass>(); Console.WriteLine(instanse.Test()); |
运行结果如下图所示:
执行得到和Python代码完全一致的结果,至此,框架的功能实现完毕。
接下来用CodeTimer对框架的性能做个简单的测试,分别对比无代理方法,动态代理方法,手动继承方法的执行效率进行测试,测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | var instanse = TypeFactory.CreateInstanse<TestClass>(); TestClass rawInstanse = new TestClass(); TestClassStatic staticInstanse = new TestClassStatic(); //first call rawInstanse.Test2(); instanse.Test2(); staticInstanse.Test2(); CodeTimer.Initialize(); int num = 10000; CodeTimer.Time( "Raw Type" , num, () => { rawInstanse.Test2(); }); CodeTimer.Time( "Proxy Type" , num, () => { instanse.Test2(); }); CodeTimer.Time( "Static Type" , num, () => { staticInstanse.Test2(); }); |
看到结果很是让人始料不及,代理方法和手写方法执行速度一致倒是在预料之中,毕竟IL是完全一样的,但是有Decorator和无Decorator的效率差距实在无法让人接受,很显然,我们代理方法的实现效率上无法过关,因此,本框架虽然功能上已经达到要求,但是性能上还有很大的优化空间。
六. 本篇小结
在本篇中,我们完成了框架的基本设计,让框架成功的实现了我们的功能目标,但是,通过测试表明,框架的性能还远不能达到我们的要求,因此,在下一篇中(暂定名:优化篇),我们将详细分析框架的性能瓶颈并进行优化,使得本框架达到简单高效的结果。
最后提供目前框架的全部源码下载(引用其他源码的版权归原作者所有)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异