如何在使用Attribute装饰的方法中注入/生成管道代码?

时间:2009-10-04 19:44:15

标签: c# attributes code-generation postsharp syntactic-sugar

我正在阅读一些关于缓存和记忆的文章,以及如何使用委托和泛型轻松实现它。语法非常简单,实现起来非常简单,但我觉得由于重复性,应该可以基于属性生成代码,而不必反复编写相同的管道代码。

假设我们从默认示例开始:

class Foo
{
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

然后记住这个:

// 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;
  }
}

我想,一旦它找到一个与Memoize扩展方法之一匹配的标记方法,那么只需创建一个吐出这段代码的代码生成器就不会更简单了。因此,我只需要添加一个属性:

,而不是编写这个管道代码
class Foo
{
  [Memoize]
  public int Fibonacci(int n)
  {
    return n > 1 ? Fibonacci(n-1) + Fibonacci(n-2) : n;
  }
}

老实说,我知道这看起来更像是应该由预处理器转换而不是实际代码生成的编译器糖,但我的问题是:

  1. 您认为在c#源文件中找到具有给定属性的方法,解析出参数类型和返回类型以及生成与此指纹匹配的委托的最佳方法
  2. 将此功能集成到构建过程中的最佳方法是什么,而不会实际覆盖我的代码。是否可以在将源文件传递给编译器之前对源文件进行一些预处理?
  3. 感谢您提出的所有想法。

    更新

    我已经按照Shay的建议调查了Postsharp库,它似乎非常适合交易管理,跟踪或安全等非时间关键型应用程序。

    然而,当在时间关键的上下文中使用它时,它证明比委托更慢。每次实现的Fibonacci示例的一百万次迭代导致运行时间减慢80倍。 (0.012ms postharp vs。每次通话0.00015ms代表)

    但老实说,在我打算使用它的上下文中,结果是完全可以接受的。感谢您的回复!

    UPDATE2

    显然,Postsharp的作者正在努力研究release 2.0,其中包括生成的代码中的性能改进和编译时间。

4 个答案:

答案 0 :(得分:6)

答案 1 :(得分:3)

我在我的一个项目中使用了以下Memoize函数:

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));
    }
}

在我的项目中,我只需要使用一个实现IEquatable<Т>的参数的函数,但这可以进一步推广。 另一个重要的评论是这段代码不是线程安全的。如果需要线程安全性,则需要同步对内部映射哈希表的读/写访问权限。

答案 2 :(得分:1)

专门针对您的观点:

  1. 这可能太难了 以你描述的方式,如 你需要一个完整的C#语法 解析器。什么可能更可行 替代方案是编写托管应用程序 这可以加载编译的 装配和提取类型 信息使用反射。这个 将涉及获得所有类型 给定组件中的对象,看 对于类型的方法,检索 自定义属性,然后 发出记忆代码(这个 部分可能有点困难)。
  2. 如果你走的是我在#1中提到的路线,你可以简单地说 添加生成后步骤以运行您的工具。视觉工作室 (它使用下面的MSBuild)使这相对容易。

答案 3 :(得分:1)

如果您向PostSharp编写插件而不是使用其LAOS库,则不会受到性能影响。