I was reading through some articles on Caching and Memoization and how to implement it easily using delegates and generics. The syntax was pretty straightforward, and it is surprisingly easy to implement, but I just feel due to the repetitive nature it should be possible to generate code based on an Attribute, instead of having to write the same plumbing code over and over.
Let's say we start off with the default example:
class Foo
{
public int Fibonacci(int n)
{
return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
}
}
And then to memoize this:
// Let's say we have a utility class somewhere with the following extension method:
// public static Func<TResult> Memoize<TResult>(this Func<TResult> f)
class Foo
{
public Func<int,int> Fibonacci = fib;
public Foo()
{
Fibonacci = Fibonacci.Memoize();
}
public int fib(int n)
{
return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
}
}
I thought, wouldn't it be simpler to just make a code generator that spits out this code, once it finds a tagged method that matches one of the Memoize extension methods. So in stead of writing this plumbing code, I could just add an attribute:
class Foo
{
[Memoize]
public int Fibonacci(int n)
{
return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
}
}
Honestly, I know this is looking more like compiler sugar that should be converted by a preprocessor than actual code generation but my question is:
Thanks for any and all ideas.
Update:
I have looked into the Postsharp library as Shay had suggested, and it seemed very suited for the job on non time-critical applications like Transaction Management, Tracing or Security.
However when using it in a time-critical context it proved a LOT slower than the delegate. One million iterations of the Fibonacci example with each implementation resulted in a 80x slower runtime. (0.012ms postsharp vs 0.00015ms delegate per call)
But honestly the result is completely acceptable in the context in which I intend to use it. Thanks for the responses!
Update2:
Apparently the author of Postsharp is working hard on a release 2.0 which will include, among other things, performance improvements in produced code, and compile time.
I bump into this memoizer attribute using postsharp
I've used the following Memoize function in a project of mine:
public class Foo
{
public int Fibonacci(int n)
{
return n > 1 ? Fibonacci(n - 1) + Fibonacci(n - 2) : n;
}
}
class Program
{
public static Func<Т, TResult> Memoize<Т, TResult>(Func<Т, TResult> f) where Т : IEquatable<Т>
{
Dictionary<Т, TResult> map = new Dictionary<Т, TResult>();
return a =>
{
TResult local;
if (!TryGetValue<Т, TResult>(map, a, out local))
{
local = f(a);
map.Add(a, local);
}
return local;
};
}
private static bool TryGetValue<Т, TResult>(Dictionary<Т, TResult> map, Т key, out TResult value) where Т : IEquatable<Т>
{
EqualityComparer<Т> comparer = EqualityComparer<Т>.Default;
foreach (KeyValuePair<Т, TResult> pair in map)
{
if (comparer.Equals(pair.Key, key))
{
value = pair.Value;
return true;
}
}
value = default(TResult);
return false;
}
static void Main(string[] args)
{
var foo = new Foo();
// Transform the original function and render it with memory
var memoizedFibonacci = Memoize<int, int>(foo.Fibonacci);
// memoizedFibonacci is a transformation of the original function that can be used from now on:
// Note that only the first call will hit the original function
Console.WriteLine(memoizedFibonacci(3));
Console.WriteLine(memoizedFibonacci(3));
Console.WriteLine(memoizedFibonacci(3));
Console.WriteLine(memoizedFibonacci(3));
}
}
In my project I needed only functions with a single argument that implement IEquatable<Т> but this can be generalized even further. Another important remark is that this code is not thread safe. If you need thread safety you will need to synchronize the read/write access to the internal map hashtable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With