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:写文章真累,有点虎头蛇尾了,太困了。

原文地址:https://www.cnblogs.com/tubo/p/1562933.html