LazyWeak: A Low Memory Implementation of Lazy

Lazy<T> is a new class in .NET 4, but I imagine most developers have been running their own versions for years.  It’s used to avoid the fetching of expensive resources.  However, the fundamental assumption is that it’s the fetching of T that is expensive.  Assume, instead, that the cost of fetching/calculating T isn’t your main concern, but the memory storage cost.  For this, you want something with slightly different characteristics:

  • T is computed when needed.
  • T is cached
  • T is thrown away if the memory is at a premium.
  • T can be recreated if necessary
  • The external API should be the same as Lazy<T>

Sadly, the last isn’t quite possible, since Microsoft didn’t create an ILazy<T> while they were there.  (One day someone at Microsoft will read this and figure out where they’ve been going wrong.)  So, this is as close as I can manage:

using System;

/// <summary>
/// Like Lazy, only can recreate the object on demand
/// </summary>
/// <typeparam name="T"></typeparam>
public class LazyWeak<T> 
{
    private static readonly object __noObject = 3;

    private readonly Func<T> _factory;
    private readonly WeakReference _reference;


    public LazyWeak(Func<T> factory, T initial) {
        _factory = factory;
        _reference = new WeakReference(initial);
    }

    public LazyWeak(Func<T> factory) 
    {
        _factory = factory;
        _reference = new WeakReference(__noObject);
    }

    public bool IsValueCreated
    {
        get
        {
            return _reference.IsAlive && !ReferenceEquals(_reference.Target, __noObject);
        }
    }

    public T Value
    {
        get
        {
            var result = _reference.Target;
            if (ReferenceEquals(result, __noObject) || !_reference.IsAlive)
            {
                _reference.Target = result = _factory();
                
            }
            return (T)result;
        }
    }
}

You can create the LazyWeak<T> with an initial value.  This makes no sense in the case of Lazy<T>, but can be pretty useful here.  I’ve also aimed to ensure that the code is thread-safe.  If you spot a bug, let me know.

Finally, a quick test:

public class LazyTest
{
    static void Main()
    {
        int calls = 0;
        Func<int> factory = () => { calls++; return 7; };
        var lazy = new LazyWeak<int>(factory, 7);
        Console.WriteLine(string.Format("{0}:{1}", lazy.Value, calls));
        Console.WriteLine(string.Format("{0}:{1}", lazy.Value, calls));
        Console.WriteLine("GC");
        System.GC.Collect();
        Console.WriteLine(string.Format("{0}:{1}", lazy.Value, calls));
    }
}

There are lots of advantages of relying on the garbage collector, but you lose control over what’s going on. 

So, what’s it good for?  Well, it uses less memory than just storing T or Lazy<T> and it’s faster than repeatedly calling Func<T>.  So, it falls between those two, which may be the performance sweet spot for some problem in your application.  On the other hand, it’s fairly important that T is immutable.  There’s nothing stopping you changing T, but it would revert back to its original state each time the garbage collector ran.

It’s worth mentioning that this is one of many partial solutions to the long queue problem: stick LazyWeak<T> instead of T on the queue.  This can allow you to just keep keys in memory and leave values persisted elsewhere.  If you want to do both, you want a proper external queue.

Published by

Julian Birch

Full time dad, does a bit of coding on the side.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s