Emit实现简单的C# AOP框架
1.抽象属性基类
1 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 2 public abstract class AspectAttribute : Attribute 3 { 4 public Type CallHandlerType { get; protected set; } 5 6 protected abstract PropertyInfo[] PropertiesInfo { get; } 7 8 public NameValueCollection GetAttrs() 9 { 10 NameValueCollection attrs = new NameValueCollection(); 11 12 foreach (var p in PropertiesInfo) 13 { 14 attrs.Add(p.Name, p.GetValue(this, null).ToString()); 15 } 16 17 return attrs; 18 } 19 }
2.属性实现
1 public class CacheAttribute : AspectAttribute 2 { 3 private readonly static PropertyInfo[] Properties = typeof(CacheAttribute).GetProperties(BindingFlags.Public | BindingFlags.Instance); 4 5 public CacheAttribute() 6 { 7 CallHandlerType = typeof(CacheCallHandler); 8 } 9 10 public string CacheKey { get; set; } 11 12 public int DurationMinutes { get; set; } 13 14 protected override PropertyInfo[] PropertiesInfo 15 { 16 get { return Properties; } 17 } 18 }
3.事件回调接口
1 public interface ICallHandler 2 { 3 void BeginInvoke(MethodContext context); 4 5 void EndInvoke(MethodContext context); 6 7 void OnException(MethodContext context); 8 }
4.注册事件实现(必须实现带参数的构造函数,也可在ICallHandler注册一个Init方法来代替)
1 public class CacheCallHandler : ICallHandler 2 { 3 private const int DefaultDurationMinutes = 30; 4 5 private string _cacheKey; 6 private int _durationMinutes; 7 8 public CacheCallHandler(NameValueCollection attributes) 9 { 10 _cacheKey = String.IsNullOrEmpty(attributes["CacheKey"]) ? string.Empty : attributes["CacheKey"]; 11 _durationMinutes = String.IsNullOrEmpty(attributes["DurationMinutes"]) ? DefaultDurationMinutes : int.Parse(attributes["DurationMinutes"]); 12 } 13 14 public void BeginInvoke(MethodContext context) 15 { 16 //context.ReturnValue = "Cache Result"; 17 //context.Processed = true; 18 } 19 20 public void EndInvoke(MethodContext context) 21 { 22 } 23 24 public void OnException(MethodContext context) 25 { 26 //Console.WriteLine(context.Exception.Message); 27 } 28 }
5.方法执行上下文
1 public class MethodContext 2 { 3 /// <summary> 4 /// 5 /// </summary> 6 public object Executor { get; set; } 7 8 /// <summary> 9 /// 10 /// </summary> 11 public string ClassName { get; set; } 12 13 /// <summary> 14 /// 15 /// </summary> 16 public string MethodName { get; set; } 17 18 /// <summary> 19 /// 20 /// </summary> 21 public object ReturnValue { get; set; } 22 23 /// <summary> 24 /// 25 /// </summary> 26 public bool Processed { get; set; } 27 28 /// <summary> 29 /// 30 /// </summary> 31 public object[] Parameters { get; set; } 32 33 /// <summary> 34 /// 35 /// </summary> 36 public Exception Exception { get; set; } 37 }
6.AOP代理类实现
AOPFactory
1 public class AOPFactory 2 { 3 private const string ASSEMBLY_NAME = "EmitWraper"; 4 private const string MODULE_NAME = "EmitModule_"; 5 private const string TYPE_NAME = "Emit_"; 6 private const TypeAttributes TYPE_ATTRIBUTES = TypeAttributes.Public | TypeAttributes.Class; 7 private const MethodAttributes METHOD_ATTRIBUTES = MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual; 8 9 private static readonly Hashtable typeCache = Hashtable.Synchronized(new Hashtable()); 10 11 /// <summary> 12 /// CreateInstance from T 13 /// </summary> 14 /// <typeparam name="T"></typeparam> 15 /// <param name="parameters"></param> 16 /// <returns></returns> 17 public static T CreateInstance<T>(params object[] parameters) where T : class, new() 18 { 19 Type baseType = typeof(T); 20 Type proxyType = typeCache[baseType] as Type; 21 22 if (proxyType == null) 23 { 24 lock (typeCache.SyncRoot) 25 { 26 proxyType = BuilderType(baseType); 27 typeCache.Add(baseType, proxyType); 28 } 29 } 30 31 return (T)Activator.CreateInstance(proxyType, parameters); 32 //return (T)FastObjectCreator.CreateObject(proxyType, parameters); 33 } 34 35 #region build proxy type 36 37 #region BuilderType 38 private static Type BuilderType(Type baseType) 39 { 40 AssemblyName an = new AssemblyName { Name = ASSEMBLY_NAME }; 41 an.SetPublicKey(Assembly.GetExecutingAssembly().GetName().GetPublicKey()); 42 43 AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); 44 ModuleBuilder module = assembly.DefineDynamicModule(MODULE_NAME + baseType.Name); 45 46 TypeBuilder typeBuilder = module.DefineType(TYPE_NAME + baseType.Name, TYPE_ATTRIBUTES, baseType); 47 48 BuildConstructor(baseType, typeBuilder); 49 50 BuildMethod(baseType, typeBuilder); 51 52 Type type = typeBuilder.CreateType(); 53 54 return type; 55 } 56 #endregion 57 58 #region BuildConstructor 59 /// <summary> 60 /// 61 /// </summary> 62 /// <param name="baseType"></param> 63 /// <param name="typeBuilder"></param> 64 private static void BuildConstructor(Type baseType, TypeBuilder typeBuilder) 65 { 66 foreach (var ctor in baseType.GetConstructors(BindingFlags.Public | BindingFlags.Instance)) 67 { 68 var parameterTypes = ctor.GetParameters().Select(u => u.ParameterType).ToArray(); 69 var ctorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes); 70 71 ILGenerator il = ctorBuilder.GetILGenerator(); 72 for (int i = 0; i <= parameterTypes.Length; ++i) 73 { 74 LoadArgument(il, i); 75 } 76 il.Emit(OpCodes.Call, ctor); 77 il.Emit(OpCodes.Ret); 78 } 79 } 80 #endregion 81 82 #region BuildMethod 83 /// <summary> 84 /// 85 /// </summary> 86 /// <param name="baseType"></param> 87 /// <param name="typeBuilder"></param> 88 /* 89 [Cache(CacheKey = "TestKey", DurationMinutes = 35)] 90 public virtual int MockAopTest(int i) 91 { 92 int result = 0; 93 MethodContext context = new MethodContext(); 94 context.ClassName = "AopTestClass"; 95 context.MethodName = "MockAopTest"; 96 context.Executor = this; 97 context.Parameters = new object[1]; 98 context.Parameters[0] = i; 99 context.Processed = false; 100 context.ReturnValue = result; 101 102 ICallHandler[] handlers = new ICallHandler[1]; 103 NameValueCollection attrCollection = new NameValueCollection(); 104 attrCollection.Add("CacheKey", "TestKey"); 105 attrCollection.Add("DurationMinutes", "35"); 106 handlers[0] = new CacheCallHandler(attrCollection); 107 108 for (int c = 0; c < handlers.Length; ++c) 109 { 110 handlers[c].BeginInvoke(context); 111 } 112 113 if (context.Processed == false) 114 { 115 result = TestMethod2(i); 116 context.ReturnValue = result; 117 } 118 119 for (int c = 0; c < handlers.Length; ++c) 120 { 121 handlers[c].EndInvoke(context); 122 } 123 124 return result; 125 } 126 */ 127 private static void BuildMethod(Type baseType, TypeBuilder typeBuilder) 128 { 129 foreach (var methodInfo in baseType.GetMethods()) 130 { 131 if (!methodInfo.IsVirtual && !methodInfo.IsAbstract) continue; 132 object[] attrs = methodInfo.GetCustomAttributes(typeof(AspectAttribute), true); 133 int attrCount = attrs.Length; 134 if (attrCount == 0) continue; 135 136 ParameterInfo[] parameters = methodInfo.GetParameters(); 137 Type[] parameterTypes = parameters.Select(u => u.ParameterType).ToArray(); 138 139 MethodBuilder methodBuilder = typeBuilder.DefineMethod( 140 methodInfo.Name, 141 METHOD_ATTRIBUTES, 142 methodInfo.ReturnType, 143 parameterTypes); 144 145 for (int i = 0; i < parameters.Length; i++) 146 { 147 methodBuilder.DefineParameter(i + 1, parameters[i].Attributes, parameters[i].Name); 148 } 149 methodBuilder.SetParameters(parameterTypes); 150 151 ILGenerator il = methodBuilder.GetILGenerator(); 152 153 // MethodContext context = new MethodContext(); 154 LocalBuilder localContext = il.DeclareLocal(typeof(MethodContext)); 155 156 #region init context 157 il.Emit(OpCodes.Newobj, typeof(MethodContext).GetConstructor(Type.EmptyTypes)); 158 il.Emit(OpCodes.Stloc, localContext); 159 // context.MethodName = m.Name; 160 il.Emit(OpCodes.Ldloc, localContext); 161 il.Emit(OpCodes.Ldstr, methodInfo.Name); 162 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_MethodName"), new[] { typeof(string) }); 163 // context.ClassName = m.DeclaringType.Name; 164 il.Emit(OpCodes.Ldloc, localContext); 165 il.Emit(OpCodes.Ldstr, methodInfo.DeclaringType.Name); 166 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_ClassName"), new[] { typeof(string) }); 167 // context.Executor = this; 168 il.Emit(OpCodes.Ldloc, localContext); 169 il.Emit(OpCodes.Ldarg_0); 170 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_Executor"), new[] { typeof(object) }); 171 #endregion 172 173 // set context.Parameters 174 #region context.Parameters = new object[Length]; 175 LocalBuilder tmpParameters = il.DeclareLocal(typeof(object[])); 176 il.Emit(OpCodes.Ldc_I4, parameters.Length); 177 il.Emit(OpCodes.Newarr, typeof(object)); 178 il.Emit(OpCodes.Stloc, tmpParameters); 179 for (int i = 0; i < parameters.Length; ++i) 180 { 181 il.Emit(OpCodes.Ldloc, tmpParameters); 182 il.Emit(OpCodes.Ldc_I4, i); 183 il.Emit(OpCodes.Ldarg, i + 1); 184 il.Emit(OpCodes.Box, parameterTypes[i]); 185 il.EmitCall(OpCodes.Call, typeof(object).GetMethod("GetType", new Type[] { }), null); 186 il.Emit(OpCodes.Stelem_Ref); 187 } 188 il.Emit(OpCodes.Ldloc, localContext); 189 il.Emit(OpCodes.Ldloc, tmpParameters); 190 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_Parameters"), new[] { typeof(object[]) }); 191 #endregion 192 193 LocalBuilder localReturnValue = null; 194 if (methodInfo.ReturnType != typeof(void)) // has return value 195 { 196 localReturnValue = il.DeclareLocal(methodInfo.ReturnType); 197 } 198 199 // ICallHandler[] handlers = new ICallHandler[attrCount]; 200 LocalBuilder localHandlers = il.DeclareLocal(typeof(ICallHandler[])); 201 il.Emit(OpCodes.Ldc_I4, attrCount); 202 il.Emit(OpCodes.Newarr, typeof(ICallHandler)); 203 il.Emit(OpCodes.Stloc, localHandlers); 204 205 // create ICallHandler instance 206 #region create ICallHandler instance 207 for (int i = 0; i < attrCount; ++i) 208 { 209 LocalBuilder tmpNameValueCollection = il.DeclareLocal(typeof(NameValueCollection)); 210 il.Emit(OpCodes.Newobj, typeof(NameValueCollection).GetConstructor(Type.EmptyTypes)); 211 il.Emit(OpCodes.Stloc, tmpNameValueCollection); 212 213 AspectAttribute attr = (attrs[i] as AspectAttribute); 214 NameValueCollection attrCollection = attr.GetAttrs(); 215 foreach (var key in attrCollection.AllKeys) 216 { 217 il.Emit(OpCodes.Ldloc, tmpNameValueCollection); 218 il.Emit(OpCodes.Ldstr, key); 219 il.Emit(OpCodes.Ldstr, attrCollection[key]); 220 il.Emit(OpCodes.Callvirt, typeof(NameValueCollection).GetMethod("Add", new[] { typeof(string), typeof(string) })); 221 } 222 223 il.Emit(OpCodes.Ldloc, localHandlers); 224 il.Emit(OpCodes.Ldc_I4, i); 225 il.Emit(OpCodes.Ldloc, tmpNameValueCollection); 226 il.Emit(OpCodes.Newobj, attr.CallHandlerType.GetConstructor(new[] { typeof(NameValueCollection) })); 227 il.Emit(OpCodes.Stelem_Ref); 228 } 229 #endregion 230 231 // BeginInvoke 232 for (int i = 0; i < attrCount; ++i) 233 { 234 il.Emit(OpCodes.Ldloc, localHandlers); 235 il.Emit(OpCodes.Ldc_I4, i); 236 il.Emit(OpCodes.Ldelem_Ref); 237 il.Emit(OpCodes.Ldloc, localContext); 238 il.Emit(OpCodes.Callvirt, typeof(ICallHandler).GetMethod("BeginInvoke")); 239 } 240 241 Label endLabel = il.DefineLabel(); // if (context.Processed) goto: ... 242 il.Emit(OpCodes.Ldloc, localContext); 243 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("get_Processed"), Type.EmptyTypes); 244 il.Emit(OpCodes.Ldc_I4_1); 245 il.Emit(OpCodes.Beq, endLabel); 246 247 // excute base method 248 LocalBuilder localException = il.DeclareLocal(typeof(Exception)); 249 il.BeginExceptionBlock(); // try { 250 251 il.Emit(OpCodes.Ldloc, localContext); 252 253 il.Emit(OpCodes.Ldarg_0); 254 for (int i = 0; i < parameterTypes.Length; ++i) 255 { 256 LoadArgument(il, i + 1); 257 } 258 il.EmitCall(OpCodes.Call, methodInfo, parameterTypes); 259 // is has return value, save it 260 if (methodInfo.ReturnType != typeof(void)) 261 { 262 if (methodInfo.ReturnType.IsValueType) 263 { 264 il.Emit(OpCodes.Box, methodInfo.ReturnType); 265 } 266 il.Emit(OpCodes.Stloc, localReturnValue); 267 268 il.Emit(OpCodes.Ldloc, localContext); 269 il.Emit(OpCodes.Ldloc, localReturnValue); 270 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_ReturnValue"), new[] { typeof(object) }); 271 } 272 273 il.BeginCatchBlock(typeof(Exception)); // } catch { 274 // OnException 275 il.Emit(OpCodes.Stloc, localException); 276 il.Emit(OpCodes.Ldloc, localContext); 277 il.Emit(OpCodes.Ldloc, localException); 278 il.EmitCall(OpCodes.Call, typeof(MethodContext).GetMethod("set_Exception"), new[] { typeof(Exception) }); 279 280 for (int i = 0; i < attrCount; ++i) 281 { 282 il.Emit(OpCodes.Ldloc, localHandlers); 283 il.Emit(OpCodes.Ldc_I4, i); 284 il.Emit(OpCodes.Ldelem_Ref); 285 il.Emit(OpCodes.Ldloc, localContext); 286 il.Emit(OpCodes.Callvirt, typeof(ICallHandler).GetMethod("OnException")); 287 } 288 289 il.EndExceptionBlock(); // } 290 // end excute base method 291 292 il.MarkLabel(endLabel); 293 294 // EndInvoke 295 for (int i = 0; i < attrCount; ++i) 296 { 297 il.Emit(OpCodes.Ldloc, localHandlers); 298 il.Emit(OpCodes.Ldc_I4, i); 299 il.Emit(OpCodes.Ldelem_Ref); 300 il.Emit(OpCodes.Ldloc, localContext); 301 il.Emit(OpCodes.Callvirt, typeof(ICallHandler).GetMethod("EndInvoke")); 302 } 303 304 if (methodInfo.ReturnType != typeof(void)) 305 { 306 il.Emit(OpCodes.Ldloc, localReturnValue); 307 } 308 else 309 { 310 il.Emit(OpCodes.Ldnull); 311 } 312 313 il.Emit(OpCodes.Ret); 314 } 315 } 316 #endregion 317 318 #region LoadArgument 319 /// <summary> 320 /// LoadParameter 321 /// </summary> 322 /// <param name="il"></param> 323 /// <param name="index"></param> 324 public static void LoadArgument(ILGenerator il, int index) 325 { 326 switch (index) 327 { 328 case 0: 329 il.Emit(OpCodes.Ldarg_0); 330 break; 331 case 1: 332 il.Emit(OpCodes.Ldarg_1); 333 break; 334 case 2: 335 il.Emit(OpCodes.Ldarg_2); 336 break; 337 case 3: 338 il.Emit(OpCodes.Ldarg_3); 339 break; 340 default: 341 if (index <= 127) 342 { 343 il.Emit(OpCodes.Ldarg_S, index); 344 } 345 else 346 { 347 il.Emit(OpCodes.Ldarg, index); 348 } 349 break; 350 } 351 } 352 #endregion 353 354 #endregion 355 }
7.测试代码
Test Code
1 public class AopTestClass 2 { 3 public AopTestClass() 4 { 5 } 6 7 [Cache(CacheKey = "TestKey", DurationMinutes = 35)] 8 public virtual string TestMethod(string word) 9 { 10 //throw new Exception("test exception"); 11 return "Hello: " + word; 12 } 13 14 [Cache(CacheKey = "TestKey", DurationMinutes = 35)] 15 public virtual int TestMethod2(int i) 16 { 17 return i * i; 18 } 19 20 [Cache(CacheKey = "TestKey", DurationMinutes = 35)] 21 public virtual void TestMethod3(int i) 22 { 23 int result = i * i; 24 //Console.WriteLine(result); 25 } 26 27 /// <summary> 28 /// mock emit code 29 /// </summary> 30 /// <param name="i"></param> 31 /// <returns></returns> 32 [Cache(CacheKey = "TestKey", DurationMinutes = 35)] 33 public virtual int MockAopTest(int i) 34 { 35 int result = 0; 36 MethodContext context = new MethodContext(); 37 context.ClassName = "AopTestClass"; 38 context.MethodName = "MockAopTest"; 39 context.Executor = this; 40 context.Parameters = new object[1]; 41 context.Parameters[0] = i; 42 context.Processed = false; 43 context.ReturnValue = result; 44 45 ICallHandler[] handlers = new ICallHandler[1]; 46 NameValueCollection attrCollection = new NameValueCollection(); 47 attrCollection.Add("CacheKey", "TestKey"); 48 attrCollection.Add("DurationMinutes", "35"); 49 handlers[0] = new CacheCallHandler(attrCollection); 50 51 for (int c = 0; c < handlers.Length; ++c) 52 { 53 handlers[c].BeginInvoke(context); 54 } 55 56 if (context.Processed == false) 57 { 58 result = TestMethod2(i); 59 context.ReturnValue = result; 60 } 61 62 for (int c = 0; c < handlers.Length; ++c) 63 { 64 handlers[c].EndInvoke(context); 65 } 66 67 return result; 68 } 69 70 public static void Invoke() 71 { 72 AopTestClass instance = AOPFactory.CreateInstance<AopTestClass>(); 73 74 Console.WriteLine(instance.TestMethod("Jack")); 75 76 DateTime start = DateTime.Now; 77 78 for (int i = 0; i < 1000000; ++i) 79 { 80 //instance.TestMethod2(i); 81 82 //AOPFactory.CreateInstance<AopTestClass>(); 83 84 AOPFactory.CreateInstance<AopTestClass>().TestMethod2(i); 85 86 //new AopTestClass().MockAopTest(i); 87 } 88 89 Console.WriteLine((DateTime.Now - start).TotalSeconds); 90 } 91 }
经测试,Emit代码的执行效率大概是类似代码直接执行的一半,当方法本身占用的时间比较长的,开销会更小
在一些特殊场合,还是有一定使用价值的.