Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject/generate plumbing code into methods decorated with an Attribute?

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:

  1. What do you think is the best way to find the methods in a c# source file that have a given attribute, parsing out the parametertypes and returntype, and generating a delegate that matches this fingerprint
  2. What would be the best way to integrate this into the build process, without actually overwriting my code. Is it possible to do some preprocessing on the source files before passing it on to the compiler?

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.

like image 645
Yannick Motton Avatar asked Oct 04 '09 19:10

Yannick Motton


2 Answers

I bump into this memoizer attribute using postsharp

like image 149
Shay Erlichmen Avatar answered Oct 17 '22 07:10

Shay Erlichmen


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.

like image 36
Darin Dimitrov Avatar answered Oct 17 '22 06:10

Darin Dimitrov