如何将管道代码注入/生成到用属性装饰的方法中?

发布于 2024-08-06 04:05:35 字数 1630 浏览 5 评论 0 原文

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

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

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 库,它似乎非常适合非时间关键型应用程序(如事务管理、跟踪或安全)的工作。

然而,当在时间紧迫的环境中使用它时,它比委托慢很多。每次实现斐波那契示例的一百万次迭代都会导致运行时间减慢 80 倍。 (0.012ms postsharp vs 0.00015ms delegate per call)

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

更新2

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

冷月断魂刀 2024-08-13 04:05:35

我在我的项目中使用了以下 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<Т> 但这可以进一步概括。
另一个重要的说明是这段代码不是线程安全的。如果需要线程安全,则需要同步对内部映射哈希表的读/写访问。

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.

冷…雨湿花 2024-08-13 04:05:35

具体阐述您的观点:

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

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.
救星 2024-08-13 04:05:35

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

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文