The new LazyLoader
Finally, the LazyLoader class. Credit goes to Kevin & Cyrus (who doesn't have a blog).
delegate T Creator<T>();
class LazyLoader<T>
{
IOptional<T> value = new None<T>();
readonly ILock @lock;
readonly Creator<T> create;
public LazyLoader(ILock @lock, Creator<T> create)
{
System.Diagnostics.Debug.Assert(@lock != null);
System.Diagnostics.Debug.Assert(create != null);
if (object.ReferenceEquals(@lock, null))
{
throw new ArgumentNullException("@lock");
}
if (object.ReferenceEquals(create, null))
{
throw new ArgumentNullException("create");
}
this.create = create;
this.@lock = @lock;
}
public T Value()
{
EnsureHaveValue();
return value.Value;
}
void EnsureHaveValue()
{
// PERF: add 'if (value is None<T>)' here
using (this.@lock.Aquire())
{
if (value is None<T>)
{
value = new Some<T>(this.create());
}
}
}
}
To use it, you just need to pass in a locking strategy & and creation delegate.
To make consuming it a little cleaner, we then wrote a factory with 4 variants:
static class LazyCreatorFactory
{
public static Creator<T> Create<T>(ILock @lock, Creator<T> create)
{
return new Creator<T>(new LazyLoader<T>(@lock, create).Value);
}
public static Creator<T> CreateLocked<T>(Creator<T> create)
{
return Create<T>(new Lock(), create);
}
public static Creator<T> CreateUnlocked<T>(Creator<T> create)
{
return Create<T>(new NoLock(), create);
}
public static Creator<T> CreateUnlocked<T>() where T : new()
{
return Create<T>(new NoLock(), delegate { return new T(); });
}
public static Creator<T> CreateLocked<T>() where T : new()
{
return Create<T>(new Lock(), delegate { return new T(); });
}
}
There are two pivot points: lock or not / default ctor or delegate.
Here are some examples of how it looks when you consume it:
class C
{
static Creator<Bitmap> getBitmap = LazyCreatorFactory.CreateLocked<Bitmap>(delegate { return (Bitmap)Bitmap.FromFile(@"c:\jaybaz.bmp"); });
// simple example of using the default ctor
static Creator<object> getObject = LazyCreatorFactory.CreateUnlocked<object>();
public Bitmap Bitmap
{
get
{
return getBitmap();
}
}
static void Main(string[] args)
{
// verify that this really only creates one.
Bitmap m = getBitmap();
Bitmap p = getBitmap();
System.Diagnostics.Debug.Assert(object.ReferenceEquals(m, p));
}
}
Cyrus was pretty excited about this at the end of the session. He said he was going to run back to his office & retrofit his code to use the LazyLoader and the ILock stuff.
Comments
- Anonymous
May 06, 2004
So what is this "@" before the "lock"? ("@lock")?
New kind of syntax? - Anonymous
May 07, 2004
public static Creator<T> Create<T>(ILock @lock, Creator<T> create)
{
return new Creator<T>(new LazyLoader<T>(@lock, create).Value);
}
At first I was confused by that code (generics+delegate make it a bit hard to read, imo), because it looks like Value is actually called there (but it's in fact a method, not a property...).
Maybe you could add some comment there: // Makes a lazy/thread-safe Creator out of Creator
Is there a way to remove the "new Creator<T>(" part?
Maybe the implicit delegate syntax could help making it easier to read:
Creator<T> result = new LazyLoader(...).Value; return result; // (is the syntax valid?)
What's the @ in @lock? - Anonymous
May 07, 2004
When you write a name in C#, you can optionally prefix it with @. If you do, you're telling the compiler that it's a name, not a keyword.
In this case, we wanted to call the lock "lock", but that's already a keyword in the language, so we escape it with @.
It's not a very good practice, IMO, but we liked the name. - Anonymous
May 07, 2004
Julien: Your confusion around the factory method is reasonable. We've got a method (Value) that looks a lot like a property. We use it without parens, so it looks even more like a method.
What we really want is to be able to use a property to satisfy a delegate, but the language doesn't allow that.
This concern has changed the latest implementation we came up with, which I'll post later. - Anonymous
May 07, 2004
Julien, we discussed removing the "new Creator<T>" from the method but decided against it because we thought the implicit conversion from method to delegate was very unclear.
When i instantiate a delegate i know that i must be passing it a method. However, if you just see:
Creator<T> result = new LazyLoader(...).Value;
return result;
or
return new LazyLoader(...).Value
I find it ambiguous and I end up asking myself: "Is Value a method? Is it a property that returns a Creator<T>?" - Anonymous
June 16, 2009
PingBack from http://fixmycrediteasily.info/story.php?id=4666