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代码的执行效率大概是类似代码直接执行的一半,当方法本身占用的时间比较长的,开销会更小

在一些特殊场合,还是有一定使用价值的.

 

完整代码下载

posted @ 2012-06-25 10:34  ahui  阅读(6431)  评论(4编辑  收藏  举报