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 (8)

  1. Uwe says:

    So what is this "@" before the "lock"? ("@lock")?

    New kind of syntax?

  2. 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?

  3. jaybaz [MS] says:

    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.

  4. jaybaz [MS] says:

    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.

  5. Cyrus Najmabadi says:

    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>?"