← Back to all articles

Bad Memories – Episode 1

I've discussed memoization a few times. Here is the implementation I used last time.

public static Func<T> Memoize<T>(this Func<T> f)
{
    var gate = new object();
    var set = false;
    T result = default(T);
    return () =>
    {
        if (!set)
        {
            lock (gate)
            {
                if (!set)
                {
                    result = f();
                    set = true;
                }
            }
        }
        return result;
    };
}

Strictly speaking though there is a bit of a problem. Consider the following;

Func<int> f = () =>
{
    "I'm making side effects".Dump();
    throw new Exception("42");
};

var memoizef = f.Memoize();
try{ memoizef(); } catch{}
try{ memoizef(); } catch{}

OUTPUT

I'm making side effects
I'm making side effects

In the case of exceptions, we are reproducing the side effects of the underlying function. Here is a tweaked implementation that handles exceptions. There is however an interesting problem with it. Can you spot it?

public static Func<T> Memoize<T>(this Func<T> f)
{
    var gate = new object();
    var set = false;
    T result = default(T);
    Exception error = null;
    return () =>
    {
        if (!set)
        {
            lock (gate)
            {
                if (!set)
                {
                    try
                    {
                        result = f();
                    }
                    catch(Exception ex)
                    {
                        error = ex;
                    }
                    set = true;
                }
            }
        }
        if(error != null) throw error;
        return result;
    };
}

More soon,
James

Comments