.Net中的AOP系列之《拦截位置》
本篇目录
本节的源码本人已托管于Coding上:点击查看。
本系列的实验环境:VS 2013 Update 5(建议最好使用集成了Nuget的VS版本,VS Express版也够用),安装了PostSharp。
至今,我们的关注点都是集中在方法上,本节,就看一下位置,这里的位置指的是字段或属性。位置拦截是AOP框架不太通用的功能,因此,本节大多数的例子都是使用支持位置的PostSharp框架,此外,这节还会看到一个特殊的AOP工具(与常见的AOP框架截然不同),叫做PropertyChanged.Fody。
位置拦截也许很多人没有听过C#中有位置这一说,其实,一个字段或者一个属性都是一个位置。字段和属性是OOP中常见的东西,它们为类提供数据和结构。下面简单复习一下,觉得没问题的同学可以直接跳过,记住:属性只是getter/setter方法的语法糖。
.Net中的字段和属性字段是类的成员。它们可以声明为public,private,protected,internal等等,这样就可以限制访问级别了(默认是private)。
通常,如果封装很重要的话,就不会使用public字段,因此,字段通常被设置成private,然后通过访问器方法在类外面使用该字段。如果有一个private的_balance(余额)字段,那么只能通过其它对象调用Deposit(存款)或者Withdrawal(取款)方法来改变这个字段的值:
public class BankAccount { decimal _balance; public void SetBalance(decimal amount) { _balance = amount; } public decimal GetBalance(decimal amount) { return _balance; } }在C#中,我们可以使用属性语法(get 和set )来减少代码量,下面的代码中,Balance属性封装了一个私有字段_balance:
public class BankAccount { decimal _balance; public decimal Balance { get { return _balance; } set { _balance = value; } } }get 和set都是可选的:如果不需要设置一个字段的值,那么就不需要写setter,getter同样如此。但是,这后面,.Net编译器帮我们创建了方法,如果使用反编译工具如ILSpy看一下IL代码,就会发现编译器创建了一个decimal get_Balance()方法和一个void set_Balance(decimal)方法:
.class public auto ansi beforefieldinit MyBankingProject.BankAccount extends [mscorlib]System.Object { .field private valuetype [mscorlib]System.Decimal _balance .method public hidebysig specialname instance valuetype [mscorlib]System.Decimal get_Balance () cil managed { //此处省略若干IL代码 } .method public hidebysig specialname instance void set_Balance ( valuetype [mscorlib]System.Decimal 'value' ) cil managed { //此处省略若干IL代码 } }自动属性是在C#2.0中引入的,这个工具让语法糖变得更甜了,我们甚至不需要显式创建字段就可以创建一个属性,如下:
public class MyClass { public string MyProperty {get; set;} }当使用自动属性时,必须同时使用get和set,但是可以使用不同的访问级别。比如,get可以设置成公共的,set可以设置成私有的。
对于我们.Net开发者来说,这并不是什么新鲜事儿,因为我们几乎每天都会使用这些,但是越是最常用的东西,通常你也认为最理所当然,因此,在深入涉及位置拦截的AOP代码之前有必要重温一下细节问题。
之前的教程我们知道了,AOP工具可以拦截方法,那么从上面我们又知道,属性的底层就是方法,因此,我们可以猜想可以在属性上使用方法拦截切面。事实上这是可行的,可以使用PostSharp或Castle DynamicProxy在属性上创建方法拦截。下面就是一个使用PostSharp在属性上创建方法拦截的控制台例子:
public class TestClass { public string TestProperty { get; [MyMethodAspect] set;//在一个属性的setter上使用方法拦截切面 } } [Serializable] public class MyMethodAspect:MethodInterceptionAspect { public override void OnInvoke(MethodInterceptionArgs args) { Console.WriteLine("这条语句来自自定义方法拦截切面"); args.Proceed(); } } class Program { static void Main(string[] args) { var test=new TestClass(); test.TestProperty = "测试属性";//这里会调用属性的setter方法 Console.Read(); } }效果如下:
但是这样使用有几个问题:
没关系,PostSharp给我们提供了一个更方便的方法,只需要写一个类就可以处理getting和setting,还允许为字段和属性编写切面。这就是PostSharp中的LocationInterceptionAspect,下面的例子和上面的一样,只是这次使用了LocationInterceptionAspect:
[Serializable] public class MyLocationAspect:LocationInterceptionAspect { public override void OnGetValue(LocationInterceptionArgs args) { Console.WriteLine("这条语句来自位置拦截的{0}方法",MethodBase.GetCurrentMethod()); args.ProceedGetValue(); } public override void OnSetValue(LocationInterceptionArgs args) { Console.WriteLine("这条语句来自位置拦截的{0}方法", MethodBase.GetCurrentMethod()); args.ProceedSetValue(); } } public class TestClass2 { [MyLocationAspect] public string TestProperty { get; set; } } static void Main(string[] args) { //var test=new TestClass(); //test.TestProperty = "测试属性"; var test2=new TestClass2(); test2.TestProperty = "位置拦截测试"; Console.WriteLine(test2.TestProperty); Console.Read(); }Main方法中,先是给属性赋值,所以会被MyLocationAspect的OnSetValue方法拦截到,然后打印test2.TestProperty时会被OnGetValule方法拦截,因此运行结果如下:
这里新出现的args.ProceedSetValue();和args.ProceedGetValue();和之前的args.Proceed();是一样的道理,是继续执行属性方法(属性的本质就是方法)的意思。
真实案例——懒加载