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.