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

4

10

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.

Credulity answered 4/10, 2009 at 19:44 Comment(0)
P
6

I bump into this memoizer attribute using postsharp

Pinchhit answered 4/10, 2009 at 20:16 Comment(2)
This looks like a very good alternative. Going to look into it.Credulity
@Patrick, so you do all the hard work and I'm getting the up votes :)Pinchhit
F
3

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.

Final answered 4/10, 2009 at 19:59 Comment(1)
This is not my question. Check the bulletpoints at the bottom.Credulity
T
1

To specifically address your points:

  1. This would be likely too hard to do in the way that you describe, as you'd need a full-blown C# grammar parser. What might be a more viable alternative is writing a managed app which could load the compiled assembly and extract type information using Reflection. This would involve getting all Type objects in a given assembly, looking for methods on the types, retrieving the custom attribute, and then emitting the memoization code (this part might be a bit difficult).
  2. If you go the route I mention in #1, you could simply add a post-build step to run your tool. Visual Studio (which uses MSBuild underneath) makes this relatively easy.
Trophy answered 4/10, 2009 at 20:50 Comment(0)
B
1

If you write a plugin to PostSharp instead of using its LAOS library, you would get no performance hit.

Bodhisattva answered 7/10, 2009 at 22:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.