The new new lazy loader


Cyrus then incorporated the Weak/Strong reference stuff into the LazyLoader.  He also refactored the factory to give you a reliable, predictable default & be a bit simpler.  (You also need Optional<> and the Lock<> code.)


 


First, the delegate definition:


 



delegate A Creator<A>();


 


Then the LazyLoader class:


 



class LazyLoader<A>


{


    readonly IReference<A> reference;


    readonly ILock @lock;


    readonly Creator<A> create;


 


    public LazyLoader(IReference<A> reference, ILock @lock, Creator<A> create)


    {


        Debug.Assert(@lock != null);


        Debug.Assert(create != null);


        Debug.Assert(reference != null);


        if (object.ReferenceEquals(@lock, null))


        {


            throw new ArgumentNullException(“@lock”);


        }


        if (object.ReferenceEquals(create, null))


        {


            throw new ArgumentNullException(“create”);


        }


        if (object.ReferenceEquals(reference, null))


        {


            throw new ArgumentNullException(“reference”);


        }


        this.reference = reference;


        this.create = create;


        this.@lock = @lock;


    }


 


    public A Value


    {


        get


        {


            using (this.@lock.Aquire())


            {


                IOptional<A> optional = reference.Target;


                if (optional is Some<A>)


                {


                    return optional.Value;


                }


 


                A value = this.create();


                reference.Value = value;


 


                return value;


            }


        }


    }


 


    //only exists since we can’t pass the above property to a delegate’s constructor


    public A GetValue()


    {


        return this.Value;


    }


}


 


Note that Value is again a property, and GetValue is the method that we pass to a delegate.


 


Note also that there is no inheritance; it all fell out of the system.


 


And finally the factory class:


 



static class LazyCreatorFactory


{


    public static Creator<A> Create<A>(IReference<A> reference, ILock @lock, Creator<A> create)


    {


        return new Creator<A>(new LazyLoader<A>(reference, @lock, create).GetValue);


    }


 


    public static Creator<A> Create<A>(bool strong, bool locked, Creator<A> create)


    {


        IReference<A> reference;


        if (strong)


        {


            reference = new StrongReference<A>();


        }


        else


        {


            reference = new WeakReference<A>();


        }


 


        ILock @lock;


        if (locked)


        {


            @lock = new Lock();


        }


        else {


            @lock = new NoLock();


        }


 


        return Create<A>(reference, @lock, create);


    }


 


    public static Creator<T> Create<T>(bool strong, bool locked) where T : new()


    {


        return Create<T>(strong, locked, delegate { return new T(); });


    }


 


    /// a simple constructor with smart safe defaults


    public static Creator<T> Create<T>(Creator<T> create)


    {


        return Create<T>(true, true, create);


    }


}


 


Here’s what it looks like to consume it:


 



// some examples of how this looks for the consumer.


class C


{


    static Creator<Bitmap> getBitmap = LazyCreatorFactory.Create<Bitmap>(delegate { return (Bitmap)Bitmap.FromFile(@”c:\menu6.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));


    }


}


 


Note that the instance is now named like a verb (getBitmap) and there’s a trivial property that consumes it, giving you the same appearance as a regular property-with-backing-store.

Comments (15)

  1. JD says:

    This is an interesting exploration of using generic programming.

    Pardon the naivete, but how does this make the resulting code any better? I suppose that it’s better to avoid re-implementing Lazy Loading in every place it’s used.

    This is quite complex though, and I consider the code written in the class to be much less readable than it would if the lazy-loading were encapsulted in a single method on the class. I’d like to see a sample that uses an instance (not static) creator doing some reasonably instance-based work (instead of using a constant string for the bitmap), and compare it to the corresponding code without LazyLoader.

    Again, maybe I’m just missing the point and need it banged in with samples. I like the investigation regardless, very interesting.

    Side question; do you use Optional instead of Nullable for a reason?

  2. Nullable<T> is being changed to only be allowed for ValueTypes later in Whidbey. The reason for this is that the language designers want to avoid seeing API’s introduced that take Nullable<string>, and the resulting fracturing that can occur.

  3. jaybaz [MS] says:

    The code we wrote here violates YAGNI. We’ve written a lot of generality in that we don’t know we need. The LazyLoader class varies on 3 axes:

    1. Locked or not.

    2. Weak or strong reference.

    3. delegate construction or new() construction

    If you remove one of these features by selecting the option on the left, the class becomes much simpler. If you always select the option on the right, the difference is small.

    If you have a need for exactly one combination of these features, you could write that in less code. If you only need it once, you could write it in place, without a new class.

    If you need that specific combination in 20 places in your app, and no other combo, then a class that supported exactly that, with no options, would make your code well factored.

    If you need the flexibility of all 3 of the above options, then I think the code we’ve written is about as clear as it possibly can be, with the least duplication. What we’ve done is write a very full-featured solution, as simply as possible. We’re acting like library writers, not application writers. App writers should write the simplest solution, as simply as possible.

    It does assume that the reader is comfortable with generics, delegates, an OO concepts. I think that’s a perfectly reasonable assumption.

  4. Cyrus Najmabadi says:

    JD:

    When Kevin Jay and I wrote the initial implementation, we were trying to see if it was possible to cleanly and usuably extract the following common pattern from your source:

    public T SomeProperty {

    get {

    lock (this) {

    if (!someField.IsInitialized) {

    someField.Initialize();

    }

    }

    return someField;

    }

    }

    However, the locking, testing and initialization can be arbitrarily complex (although in most cases it’s just the initialization that’s complex).

    However, after writing that I realized that this wouldn’t be useful for me. Why? Because a large number of my lazy loaded properties are, in fact, weakly referenced. I don’t want to create them until necessary (costly to create) and i don’t want to keep them when the system is under memory pressure (costly to maintain). So I end up writing this code quite often:

    private WeakReference cachedFoo;

    public T Foo{

    get {

    lock (this) {

    T foo = default(T);

    bool initialized = false;

    if (cachedFoo != null) {

    object o = cachedFoo.Target;

    if (o != null) {

    foo = (T)o;

    initilized = true;

    }

    }

    if (initialized == false) {

    foo = SomeComplexBitOfCode();

    cachedFoo = new WeakReference(foo);

    }

    return foo;

    }

    }

    }

    That’s some pretty ugly code. It’s much much much uglier when you have 10 properties.

    Now I can replace it with:

    private Creator<T> getFoo = LazyLoaderCreator.Create<T>(false, true, elegate { return SomeComplexBitOfCode(); });

    public T Foo {

    get {

    return getFoo();

    }

    }

    Now, all the locking and referencing issues are taken care of for me. All i need to care about is the initialization logic. That’s a savings of 15 identical lines every time i need this pattern.

    As Jay said above, this was not meant to solve a specific application’s needs. Rather, we wanted to provide a library that people could use in their own application to solve all of these needs.

    Note: No user ever need be concerned with the implementation of LazyLoader, IOptional, ILock, or IReference. Rather, they should only care about these following two things:

    delegate A Creator<A>();

    and

    public static Creator<A> LazyLoaderCreator.Create<A>(Creator<A> create);

    And that method basically says "Given a function that will create an A, i will give you back a function that when invoked will lazily create an A and then return that on successive calls".

    So the complexity that you see is, IMO, the complexity that we’ve kept you from having to introduce into your own class _every_ place you need lazy loading.

  5. Cyrus Najmabadi says:

    Sorry if the above comment is hard to read (both codewise and grammarwise). Looks like you can’t edit a comment after you’ve posted. If anyone would like me to clarify anything about that, please let me know.

  6. JD says:

    I appreciate the explanation that shows the code you wanted to replace with it. I’m more concerned about the readbility and usability of the code that the user sees:

    private Creator<T> getFoo

    = LazyLoaderCreator.Create<T>(false, true, delegate { return SomeComplexBitOfCode(); });

    public T Foo {

    get { return getFoo(); }

    }

    I don’t think my dislike for this code is based soley on its use of generics. Maybe some of it is naming. Creating a creator, the bool params, even the delegate (though I understand that’s the most general case),

    private LazyLoader<T> lazyGetFoo

    = LazyLoaderCreator.Create<T>();

    public T Foo {

    get { return lazyGetFoo(); }

    }

    With overloads:

    LazyLoaderCreator.CreateLoaderWithLocks<T>()

    LazyLoaderCreator.CreateLoaderWithWeakReferences<T>()

    LazyLoaderCreator.CreateLoaderWithLocksAndWeakReferences<T>()

    Those aren’t the best either, maybe enums, maybe some other factorization that makes clearer what the code is doing. The maintenance programmer isn’t going to understand your brilliance without some help, is all.

  7. jaybaz [MS] says:

    JD: I hear ya. I’ve had some of the same concerns, but not sure where to go with it next.

    We’ll keep pondering.