18

可能重复:
通过属性的 C# 包装方法

我想实现这样的功能:

[Atomic]
public void Foo()
{           
    /* foo logic */
}

其中[Atomic]attribute是一个属性,它将函数逻辑包装在事务范围内:

using(var scope = new TransactionScope())
{
    /* foo logic */
    scope.Complete();
}

这样的属性怎么写?

我之前问过基本相同的问题,我知道这可以使用 AOP 来完成,但我没有提到我正在寻找一些最简单的概念验证实现或有用的文章,它们可以帮助我使用纯 .NET 编写它框架(我想使用RealProxyMarshalByRefObject类型,我已经阅读过浏览相关的问题)。

我需要完全解决这个显示的例子。这似乎是一件基本的事情,所以我想从头开始学习如何做。它现在不需要安全和灵活。

4

4 回答 4

23

这似乎是一个基本的东西......

这是(许多)易于理解概念但根本不易于实现的事物之一。

根据Oded 的回答,.NET 中的属性不执行任何操作。它们仅存在以便其他代码(或开发人员)稍后可以查看它们。把它想象成一个花哨的评论。

考虑到这一点,您可以像这样编写属性

public class AtomicAttribute : Attribute { } 

现在最困难的部分是,您必须编写一些代码来扫描该属性,并更改代码的行为。

鉴于 C# 是一种编译语言,并且鉴于 .NET CLR 的规则,理论上有 3 种方法可以做到这一点

  1. 挂钩到 C# 编译器,并使其在看到该属性时输出不同的代码。
    这似乎很好,但现在根本不可能。也许 Roslyn 项目将来可能会允许这样做,但现在,你不能这样做。

  2. 在 C# 编译器将其转换为 MSIL后,编写将扫描 .NET 程序集的内容,并更改 MSIL。
    这基本上就是PostSharp所做的。扫描和重写 MSIL很难。有诸如Mono.Cecil 之类的库可以提供帮助,但这仍然是一个非常困难的问题。它也可能会干扰调试器等。

  3. 使用 .NET Profiling API 在程序运行时监视程序,每次看到具有该属性的函数调用时,将其重定向到其他包装函数。
    这可能是最简单的选择(尽管仍然非常困难),但缺点是您的程序现在必须在探查器下运行。这在您的开发 PC 上可能没问题,但如果您尝试部署它会导致大问题。此外,使用这种方法可能会对性能造成很大影响。

在我看来,最好的办法是创建一个设置事务的包装函数,然后将其传递给执行实际工作的 lambda。像这样:

public static class Ext 
{
    public static void Atomic(Action action) 
    {
        using(var scope = new TransactionScope()) 
        {
            action();
            scope.Commit();
        }
    }
}

.....

using static Ext; // as of VS2015

public void Foo()
{
    Atomic(() => {
        // foo logic
    }
}

这个花哨的计算机科学术语是高阶编程

于 2013-01-13T20:18:58.767 回答
10

属性是元数据——仅此而已

有许多工具可以利用此类元数据,但此类工具需要了解该属性。

PostSharp 等 AOP 工具读取此类元数据,以便了解将方面编织到代码中的内容和位置。

简而言之——仅仅写一个AtomicAttribute不会给你任何东西——你需要通过一个知道这个属性的工具传递编译的程序集并对它做“一些事情”以实现 AOP。

于 2013-01-13T19:36:01.500 回答
4

这根本不是一件基本的事情。不会因为方法具有属性而运行额外的代码,因此无处放置您的TransactionScope代码。

您需要做的是在应用程序启动时使用反射来迭代程序集中每个类的每个方法并找到标有 的方法AtomicAttribute,然后围绕该对象编写自定义代理。然后以某种方式让其他一切都调用您的代理而不是真正的实现,也许使用依赖注入框架。

大多数 AOP 框架在构建时执行此操作。例如 PostSharp 在 VisualStudio 构建您的程序集之后运行。它扫描您的程序集并重写 IL 代码以包含代理和 AOP 拦截器。这样,程序集在运行时就已准备就绪,但 IL 已与您最初编写的内容有所不同。

于 2013-01-13T20:12:53.500 回答
3

也许使用 IoC 容器解析所有对象?您可以为您的类型配置拦截器,并在其中检查调用的方法是否用该属性装饰。您可以缓存该信息,这样您就不必对每个方法调用都使用反射。

所以当你这样做时:

var something = IoC.Resolve<ISomething>();

something不是您已实现的对象,而是代理。在该代理中,您可以在方法调用之前和之后做任何您想做的事情。

于 2013-01-13T20:25:37.067 回答