之前我谈到,在普通情况下我们可以很轻松地写出过一个代理类,用来处理延迟加载的情况。当时给出了一个很简单的做法,也就是指创建基类,覆盖它的一些属性实现,类似这种:
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();
您有兴趣实现一下吗?