views:

301

answers:

3
+4  A: 

Another name for this automatic caching of function results is memoization. For a public interface, consider something along these lines:

public Func<T,TResult> Memoize<T,TResult>(Func<T,TResult> f)

... and simply use polymorphism to store T's in a dictionary of object.

Extending the delegate range could be implemented via currying and partial function application. Something like this:

static Func<T1,Func<T2,TResult>> Curry(Func<T1,T2,TResult> f)
{
    return x => y => f(x, y);
}
// more versions of Curry

Since Curry turns functions of multiple arguments into functions of single arguments (but that may return functions), the return values are eligible for memoization themselves.

Another way to do it would be to use reflection to inspect the delegate type, and store tuples in the dictionary rather than simply the argument type. A simplistic tuple would be simply an array wrapper whose hashcode and equality logic used deep comparisons and hashing.

Invalidation could be helped with weak references, but creating dictionaries with WeakReference keys is tricky - it's best done with the support of the runtime (WeakReference values is much easier). I believe there are some implementations out there.

Thread safety is easily done by locking on the internal dictionary for mutation events, but having a lock-free dictionary may improve performance in heavily concurrent scenarios. That dictionary would probably be even harder to create - there's an interesting presentation on one for Java here though.

Barry Kelly
I love curry !
BigBlondeViking
Nice, I'll read up more on Memoization. In this case, I don't think currying will really help here. Yes, it makes it possible to use this code to handle a function with multiple arguments, but it does it in a way that's not obvious to the class user, which defeats the point of the class.
Joel Coehoorn
A: 

Since this is for educational value mostly - you should take a look at the WeakReference class, which allows the GC to clear out unused handles from your class in a multi threaded environment. It's a pretty common caching pattern in .NET

That said - Caveat Emptor! Every cache is different. By building a catch-all solution, you often end up in a pathological case where your "cache" is just a glorified dictionary with lots of complicated helper methods that make your code hard t oread.

Doug
+1  A: 
LBushkin