Recently I wanted to use PostSharp for some simplistic lazy loading, but ended up hitting a couple of problems along the way. It all started very well – I defined an OnFieldAccessAttribute, removed the field in GetOptions() and overrided OnGetValue() to lazily create the type. My creation code was something like the following:
public override void OnGetValue(FieldAccessEventArgs eventArgs)
{
if (eventArgs.StoredFieldValue == null)
eventArgs.StoredFieldValue = Activator.CreateInstance(eventArgs.DeclaringType, args);
eventArgs.ExposedFieldValue = eventArgs.StoredFieldValue;
}
Big mistake. For an uninitialized field, the declaring type is null. This means that it’s impossible to apply a [LazyLoad] attribute, since the aspect is clueless about the type of object to lazily load. The solution – passing the type :( Here it is:
[Serializable]
class LazyLoad : OnFieldAccessAspect
{
private readonly Type type;
private readonly object[] args;
public LazyLoad(Type type, params object[] arguments)
{
this.type = type;
args = arguments;
}
public override OnFieldAccessAspectOptions GetOptions()
{
return OnFieldAccessAspectOptions.RemoveFieldStorage;
}
public override void OnGetValue(FieldAccessEventArgs eventArgs)
{
if (eventArgs.StoredFieldValue == null)
eventArgs.StoredFieldValue = Activator.CreateInstance(type, args);
eventArgs.ExposedFieldValue = eventArgs.StoredFieldValue;
}
}
This takes care of both the type to create and the arguments, so it can be used as follows:
class B
{
public B()
{
}
[LazyLoad(typeof(A))]
public A A;
}
You don’t want to know what happend if A is a value type – really, you don’t. But the above code works, and lazy-loads the A variable only when accessed. So far so good, right? Well, yes, except there is a problem: sometimes I want to lazy-load a member, passing this into the constructor. I could make this super-complicated by overloading aspect constructors, but I decided to create a new aspect instead:
[Serializable]
class LazyLoadWithThis : OnFieldAccessAspect
{
private readonly Type type;
public LazyLoadWithThis(Type type)
{
this.type = type;
}
public override OnFieldAccessAspectOptions GetOptions()
{
return OnFieldAccessAspectOptions.RemoveFieldStorage;
}
public override void OnGetValue(FieldAccessEventArgs eventArgs)
{
if (eventArgs.StoredFieldValue == null)
eventArgs.StoredFieldValue = Activator.CreateInstance(type, new[] {eventArgs.Instance});
eventArgs.ExposedFieldValue = eventArgs.StoredFieldValue;
}
}
There. That covers my needs. However, since there’s a myriad of Activator.CreateInstance() overloads, there’s probably a lot more one can do with lazy-loading aspects. Shame about the (somewhat) ugly syntax though – but then again, this lets you lazy-load polymorphically, if that’s your cup of tea. And I strongly suspect that lazy-loading semantics are domain-specific in most cases (they certainly are in my projects), so the above is only an example.