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