较为理想的延迟代理的编写方式
2009-09-07 11:15 by 老赵, 7402 visits之前我谈到,在普通情况下我们可以很轻松地写出过一个代理类,用来处理延迟加载的情况。当时给出了一个很简单的做法,也就是指创建基类,覆盖它的一些属性实现,类似这种:
public class LazySomeClass : SomeClass { public override int SomeID { get { return this.LazySomeID.Value; } set { this.LazySomeID.Value = value; } } public Lazy<int> LazySomeID { get; set; } }
不过我当时也提到,这么做可能够用,但是也有一些缺点。例如,它破坏了SomeID属性中包含的业务逻辑。可能SomeID原本会包含一些验证逻辑,或和另外一个属性加以同步,或发起INotifyPropertyChanging/Changed中的事件。这也是我认为NHibernate的延迟加载方法欠妥的原因,至于其他还有一些缺陷有机会在讨论。
因此我又想了想,理想中的延迟加载方式应该是什么样的呢?例如,同样是个SomeClass类,其中部分属性允许“设置”延迟加载:
public class SomeClass { public SomeClass() { } public SomeClass(int i) { } public virtual int LazyInt { get; set; } public virtual bool LazyBoolean { get; set; } public int EagerInt { get; set; } public bool EagerBoolean { get; set; } // some other members... }
如果是一个较为合理的延迟代理类,我认为它的写法应该是这样的:
public class LazySomeClass : SomeClass { public override int LazyInt { get { if (!this.m_lazyIntLoaded) { if (this.m_lazyIntLoader != null) { base.LazyInt = this.m_lazyIntLoader(); this.m_lazyIntLoader = null; } this.m_lazyIntLoaded = true; } return base.LazyInt; } set { base.LazyInt = value; this.m_lazyIntLoaded = true; this.m_lazyIntLoader = null; } } private bool m_lazyIntLoaded = false; private Func<int> m_lazyIntLoader = null; public Func<int> LazyIntLoader { get { return this.m_lazyIntLoader; } set { this.m_lazyIntLoader = value; this.m_lazyIntLoaded = false; } } }
如果我们需要为LazyInt属性设置延迟加载,那么可以设置LazyIntLoader属性,它是一个Func<int>委托对象。这种实现方式看上去复杂,不过它有一定的合理性:
- 每个Loader只执行一次,直到提供新的Loader。
- Loader执行后,会赋值给base.LazyInt,保持基类的业务逻辑。
- 从base.LazyInt读取,同样保持基类的业务逻辑。
- 如果不需要延迟加载,那么属性的行为保持不变。
其中第4点非常重要,这意味着这是一种可以“标准化”的延迟加载代理类的标准写法。我们可以在运行时使用Emit生成新的类型,继承目标类,为每个virtual属性在子类中重写一份。由于在默认情况下属性的行为不会改变,因此这样的代理类不会有问题。甚至,“辅助类库”的接口我也想好了:
var builder = LazyFactory.Create(() => new SomeClass(10) { EagerInt = 10, EagerBoolean = true }); SomeClass value = builder .Setup(c => c.LazyInt, () => GetLazyValue<int>()) .Setup(c => c.LazyBoolean, () => GetLazyValue<bool>()) .Create();
您有兴趣实现一下吗?
看下,还没想到