zoukankan      html  css  js  c++  java
  • PostSharp应用:延迟加载(二)

      在上一篇文章中简单介绍了没有使用AOP情况下如何来实现延迟加载的,并给出了一个使用了AOP实现延迟加载后的代码效果。这篇文章就来介绍如何用PostSharp来达到这种效果。

      PostSharp是在编译的时候将代码织入到你的代码中的,它是一个VisualStudio的插件,所以必须安装才能使用,去官方网站下载后安装,就可以开始你的PostSharp之旅了。

      PostSharp可以对类、类的方法、类的字段等进行拦截,而延迟加载主要是对类的属性进行拦截,C#类的属性编译后就是set和get方法,我们可以对这些方法进行拦截达到拦截属性的目的,还有一种方法就是对属性对应的字段进行拦截来达到对属性的拦截,我们就是采用的对字段进行拦截的方法:

    首先我们要引用PostSharp.Laos和PostSharp.Public,并添加相应的using语句到代码中:

    using PostSharp.Laos;

    在字段上进行拦截,我们的Attribute需要继承自OnFieldAccessAspect这个抽象类(我们的Attribute类必须是可序列化的):

        [Serializable]
        public class LazyLoadFieldAttribute : OnFieldAccessAspect
        {
            ....
        }

    仔细观察上一篇文章中以前的延迟加载的实现方式,最主要的东西就是那个委托,委托最终会被赋值为一个获取某个属性值的方法,在AOP的延迟加载中也需要这样一个方法,这个方法(名)需要通过Attribute的参数传入,光有方法名还不够,需要知道这个方法在哪个程序集的哪个类中,所以还需要传入方法所在类的FullName(包含程序集的名称),于是构造函数就如下:

        [Serializable]
        public class LazyLoadFieldAttribute : OnFieldAccessAspect
        {
            string lazyLoadMethodName;
            string typeFullName;
            public LazyLoadFieldAttribut(string lazyLoadMethodName, string typeFullName)
            {
                this.lazyLoadMethodName = lazyLoadMethodName;
                this.typeFullName = typeFullName;
            }
        }

    看到这里大家已经猜出来了吧?是的,我们要通过反射来调用获取属性值的延迟加载方法(在这里我们不讨论反射的性能问题,我在这里只是介绍使用PostSharp解决延迟加载的方法)。

    OnFieldAccessAspect提供了2个操作字段值的方法:OnGetValue和OnSetValue,通过对这2个方法的重载我们就可以对字段值进行拦截了,这2个方法接收一个声明为FieldAccessEventArgs的参数,FieldAccessEventArgs有如下的属性:

    FieldAccessEventArgs

    我们将通过这些属性来获取需要延迟加载的类的信息以及实例等。

    先来看看比较简单一点的OnSetValue的实现:

            public override void OnSetValue(FieldAccessEventArgs eventArgs)
            {
                eventArgs.StoredFieldValue = eventArgs.ExposedFieldValue;
            }

    在这里ExposedFieldValue可以理解为我们要赋给字段的值,而StoredFieldValue就是当前字段保存的值,OnSetValue只是简单地把ExposedFieldValue赋给了StoredFieldValue。OnGetValue的实现:

            public override void OnGetValue(FieldAccessEventArgs eventArgs)
            {
                if (eventArgs.StoredFieldValue == null)
                {
                    eventArgs.StoredFieldValue = LazyLoadValue(lazyLoadMethodName, typeFullName, eventArgs.Instance);
                }
                eventArgs.ExposedFieldValue = eventArgs.StoredFieldValue;
            }

    也很简单,判断字段保存的值是否为null(这里主要是为了讲原理,简单化了,实际上如果是集合或者数组,还要判断他们是否为空),如果为null则通过LazyLoadValue去获取值,而LazyLoadValue就是通过反射用当前的实例(eventArgs.Instance)作为参数去调用lazyLoadMethodName指定的方法。

      看起来很不错吧,但是等等,我们好像漏掉了什么?是的,被你发现了,当我们从数据源获取出来的值本来就是null,那不是每次都要去调用LazyLoadValue这个方法?对照以前的延迟加载的实现,我们发现少了一个isLoaded的属性,用来表明已经加载过了,不管是否为null,都不需要去数据源加载了。如何解决呢?不可能在类的定义里面去为每一个需要延迟加载的属性都声明一个isLoaded的字段吧,那代码看起来就不优雅了。

      我首先想到的是不能在类里面在直接声明,那么是否可以用织入的方式为类增加一些isLoaded的字段呢,可以我找了很久都没有找到PostSharp在类里面织入字段的方法。后来考虑到如果在类里面声明一系列的isLoaded字段不雅观,那就用一个List来保存字段名表明那些字段已经加载过了(List<string> IsLoadedFieldList)列表中有的就是加载过了,没有的就没有加载,这样就只有一个字段,比许多isLoaded看起来要舒服点。最终我也放弃了这种做法,因为CompositionAspect出场了:

    CompositionAspect

    通过它,你可以给你的类增加一个接口实现,我不能在类里面直接织入一个IsLoadedFieldList字段,可以定义一个接口包含IsLoadedFieldList,然后你的类“实现”这个接口,当然不是你“显示”实现,而是PostSharp织入:

    首先定义一个接口:

        public interface ILazyLoadable
        {
            List<string> IsLoadedList { get; }
        }

    为这个接口提供一个默认实现:

        internal class LazyLoadImplementation : ILazyLoadable
        {
            List<string> isLoadedList = new List<string>();
            public List<string> IsLoadedList
            {
                get { return isLoadedList; }
            }
        }

    然后就是LazyLoadClassAttribute,这个Attribute是用在类上的:

        [Serializable]
        public class LazyLoadClassAttribute : CompositionAspect
        {
            public LazyLoadClassAttribute()
            {
            }
            public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
            {
                return new LazyLoadImplementation();
            }
    
            public override Type GetPublicInterface(Type containerType)
            {
                return typeof(ILazyLoadable);
            }
        }

    这样就需要对LazyLoadFieldAttribute的OnGetValue和OnSetValue做一点点修改:

            public override void OnGetValue(FieldAccessEventArgs eventArgs)
            {
                string fieldName = eventArgs.FieldInfo.Name;
                ILazyLoadable isLoaded = eventArgs.Instance as ILazyLoadable;
                if (isLoaded != null)
                {
                    if (eventArgs.StoredFieldValue == null && !isLoaded.IsLoadedFieldList.Contains(fieldName))
                    {
                        eventArgs.StoredFieldValue = LazyLoadValue(lazyLoadMethodName, typeFullName, eventArgs.Instance);
                        isLoaded.IsLoadedFieldList.Add(fieldName);
                    }
                }
                eventArgs.ExposedFieldValue = eventArgs.StoredFieldValue;
            }
    
            public override void OnSetValue(FieldAccessEventArgs eventArgs)
            { 
                string fieldName = eventArgs.FieldInfo.Name;
                ILazyLoadable isLoaded = eventArgs.Instance as ILazyLoadable;
                if (isLoaded != null)
                {
                    if (!isLoaded.IsLoadedFieldList.Contains(fieldName))
                    {
                        isLoaded.IsLoadedFieldList.Add(fieldName);
                    }
                }
                eventArgs.StoredFieldValue = eventArgs.ExposedFieldValue;
            }

    一个简单的延迟加载完成了。

    ps:写文章真累,有点虎头蛇尾了,太困了。

  • 相关阅读:
    poj 3264 Balanced Lineup(st/线段树)
    Linux下的硬件驱动——USB设备(下)
    linux-2.6.14下USB驱动移植心得
    Linux下的硬件驱动——USB设备(上)
    在menuconfig中配置Linux内核裁剪的具体步骤
    Linux-2.6.32.2内核在mini2440上的移植(十)---配置USB外设
    ubuntu12.04下NFS安装
    make menuconfig时提示“error opening terminal”的解决方法
    linux系统日志编程
    socat用法
  • 原文地址:https://www.cnblogs.com/tubo/p/1562933.html
Copyright © 2011-2022 走看看