10

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

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

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. 您认为在 ac# 源文件中查找具有给定属性的方法、解析参数类型和返回类型并生成与此指纹匹配的委托的最佳方法是什么
  2. 在不实际覆盖我的代码的情况下,将其集成到构建过程中的最佳方法是什么。在将源文件传递给编译器之前,是否可以对源文件进行一些预处理?

感谢您的任何想法。

更新

我已经按照 Shay 的建议研究了 Postsharp 库,它似乎非常适合处理事务管理、跟踪或安全等非时间关键型应用程序的工作。

然而,当在时间紧迫的上下文中使用它时,它被证明比委托慢很多。每次实现对 Fibonacci 示例进行 100 万次迭代会导致运行时间降低 80 倍。(每次调用 0.012ms postsharp vs 0.00015ms delegate)

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

更新2

显然 Postsharp 的作者正在努力开发2.0 版本,其中包括在生成的代码和编译时间方面的性能改进。

4

4 回答 4

6

我使用 postsharp 碰到了这个memoizer 属性

于 2009-10-04T20:16:42.563 回答
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<Т>的单个参数的函数,但这可以进一步推广。另一个重要的说明是此代码不是线程安全的。如果您需要线程安全,则需要同步对内部映射哈希表的读/写访问。

于 2009-10-04T19:59:24.550 回答
1

如果您为 PostSharp 编写插件而不是使用其 LAOS 库,则不会对性能造成影响。

于 2009-10-07T22:36:26.480 回答
1

为了具体解决您的观点:

  1. 按照您描述的方式,这可能很难做到,因为您需要一个成熟的 C# 语法解析器。可能更可行的替代方法是编写一个托管应用程序,该应用程序可以加载已编译的程序集并使用反射提取类型信息。这将涉及获取给定程序集中的所有 Type 对象,查找类型上的方法,检索自定义属性,然后发出 memoization 代码(这部分可能有点困难)。
  2. 如果您走我在 #1 中提到的路线,您可以简单地添加一个构建后步骤来运行您的工具。Visual Studio(它在下面使用 MSBuild)使这相对容易。
于 2009-10-04T20:50:14.820 回答