HTML5技术

.Net中的AOP系列之《拦截位置》 - tkb至简(2)

字号+ 作者:H5之家 来源:博客园 2016-08-30 18:00 我要评论( )

懒加载的目的就是延迟一些耗时操作的执行,相反,预加载的目的是一个或多个操作在得到结果前每次都要执行,以防需要这些操作。NHibernate和EF都是用在持久层的数据库工具,当使用懒加载从DB中检索实体时,它们只会

懒加载的目的就是延迟一些耗时操作的执行,相反,预加载的目的是一个或多个操作在得到结果前每次都要执行,以防需要这些操作。NHibernate和EF都是用在持久层的数据库工具,当使用懒加载从DB中检索实体时,它们只会拉取你需要的实体而不会拉取相关实体,相反,使用预加载,它们就会把你需要的实体(比如A),和该实体相关的实体(B),以及和B相关的实体(C)等等都会加载出来。此时,就需要在性能和方便之间进行权衡了。

.Net中的懒加载

懒加载的一种方式是使用具有字段的属性来实现。当首次使用get时,会创建一个新对象,后续再使用字段时都会像以往那样返回字段。如下所示:

SlowConstructor _myProperty; public SlowConstructor MyProperty { get { if (_myProperty == null) _myProperty = new SlowConstructor(); return _myProperty; } }

细心的你可能会发现这不是线程安全的代码,如果这是一个关注点的话,就需要放一个lock语句。这里使用双重检查的锁机制再合适不过了,因为在第一次检查和lock之间可能会发生竞争情况(race condition):

readonly object _syncRoot = new object(); SlowConstructor _myProperty; public SlowConstructor MyProperty { get { if (_myProperty == null) lock(_syncRoot) if (_myProperty == null) _myProperty = new SlowConstructor();//在第一次if检查和lock之间可能有另一个线程正在给字段赋值 return _myProperty; } }

这样,就可以使用懒加载了。你可以像平时那样访问属性,如果不用它的话,那么SlowConstructor永远都不会运行。也可以使用工厂或者IoC工具代替new来实例化对象。但无论怎样,lock,两次if检查和字段都始终是保持不变的。

从.NET 4.0开始,.Net Framework提供了System.Lazy<T>,它是一个方便类,可以使用更少的代码完成和上面相同的事情。代码如下:

var lazy = new Lazy<SlowConstructor>(()=>new SlowConstructor());

工厂代码是以Lambda表达式(匿名函数)传入的,这就告诉Lazy首次访问时使用这个代码来实例化对象,System.Lazy<T>默认也是线程安全的,因此它封装了所有的lock代码。但是,跟前面那个例子不同的是,这样字段就成了Lazy类型,而不是SlowConstructor类型,要使用SlowConstructor对象的话,还要多个步骤:SlowConstructor c = MyProperty.Value;。

现在,想要使用懒加载时有两种选择,第一种有许多样板代码和字段,第二种使用Lazy,所有的样板代码是没有了,但是必须通过Value属性来获得懒加载对象。下面使用AOP来结合一下这两种方法的优点。

使用AOP实现懒加载

结合上面两种方法的优点,那就是可以直接访问属性(不需要通过Value属性),而且也没有很多的样板代码,就像下面这个样子:

[LazyLoadGetter]//使用特性告诉PostSharp这个属性是懒加载属性 static SlowConstructor MyProperty { get { return(new SlowConstructor() ); } }

get方法体内包含了懒加载的工厂,直到get执行时才会调用,后续的get调用也会使用首次操作的结果。
下面传创建一个控制台应用,命名为LazyLoadingDemo,安装PostSharp。定义一个模拟耗时的操作SlowConstructor(比如一个调用了一个很慢的web service等):

public class SlowConstructor { public SlowConstructor() { Console.WriteLine("正在初始化SlowConstructor,请稍等..."); Thread.Sleep(5000);//睡5秒,模拟耗时操作 } public void DoSomething() { Console.WriteLine("{0}:正在处理一些业务...",DateTime.Now); } }

在Main方法种定义该类的一个属性,并连续调用该类的DoSomething方法:

class Program { static SlowConstructor SlowService { get { return new SlowConstructor();} } static void Main(string[] args) { SlowService.DoSomething(); SlowService.DoSomething(); Console.Read(); } }

这样写代码的话就应该优化了,因为每次调用属性的get方法时都会重新实例化SlowConstructor对象。
执行结果很明显,如下:

但我们这里计划的是懒加载这个属性,不需要添加所有的字段、双重检查锁,或切换使用Lazy<T>,这里我们可以创建一个切面,该切面继承PostSharp的LocationInterceptionAspect,然后把自定义的切面当作特性用在需要懒加载的属性上即可:

[Serializable] public class MyLazyLoadingGetterAspect : LocationInterceptionAspect { private object _backingField; readonly object _syncRoot = new object(); public override void OnGetValue(LocationInterceptionArgs args) { if (_backingField == null) { lock (_syncRoot) { if (_backingField == null) { args.ProceedGetValue();//继续像往常那样执行get _backingField = args.Value;//将get获得的属性值保存到支持字段中 } } args.Value = _backingField;//因为支持字段中已经有值了,直接赋值即可 } } }

虽然切面中的代码和之前的原始代码很类似,但这是在切面里面,切面可以用在很多不同的地方,在需要使用的地方只需要像特性那样使用就可以了,很方便。
首次调用get时,OnGetValue会被调用,一开始支持字段_backingField=null,因此需要像之前那样加锁并双重检查,然后args.ProceedGetValue()告诉PostSharp继续运行get中的代码,这时,就会创建一个SlowConstructor的实例,然后,就会使用get执行的结果填充args.Value。然后我们把该值存入支持字段中,方便下次使用。
之后,每个后续调用,PostSharp都会将支持字段的值设置给args.Value,因此args.ProceedGetValue()只会在首次调用,这样,就不需要每次都实例化类了。有了这个切面,我们就有了和Lazy<T>类似的语法了,而且可以直接访问属性。

直接在需要懒加载的属性上以特性的方式使用:

[MyLazyLoadingGetterAspect] static SlowConstructor SlowService { get { return new SlowConstructor();} }

执行结果如下:

从上面的运行结果可以看出,只创建了1个实例,因而,大大提高了性能。
我们都知道,字段没有get,因此对字段进行懒加载稍微有点不同。

如何懒加载字段? 使用反射的Activator

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 升讯威微信营销系统开发实践:(3)中控服务器的设计 .Net 还是 Java? - sheng.chao

    升讯威微信营销系统开发实践:(3)中控服务器的设计 .Net 还是 Java

    2016-08-26 16:00

  • 开源:ASP.NET MVC+EF6+Bootstrap开发框架 - NFine

    开源:ASP.NET MVC+EF6+Bootstrap开发框架 - NFine

    2016-08-24 11:00

  • ASP.NET 关于GridView 表格重复列合并 - 小飞飞oo

    ASP.NET 关于GridView 表格重复列合并 - 小飞飞oo

    2016-08-23 10:02

  • 拥抱.NET Core,如何开发一个跨平台类库 (1) - KAnts

    拥抱.NET Core,如何开发一个跨平台类库 (1) - KAnts

    2016-08-08 14:00

网友点评
h