次の方法で共有


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