使用标准.NET Framework的AOP属性的基本实现

可能重复:
C#wrap方法通过属性

我想实现这样的function:

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

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

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

怎么写这样的属性?

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

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

这似乎是一件基本的事……

它是(很多)很容易理解这个概念的东西之一,但实现起来并不简单。

根据Oded的回答 ,.NET中的Attributes 不做任何事情 。 它们只存在,以便其他代码(或开发人员)可以在以后查看它们。 把它想象成一个花哨的评论。

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

 public class AtomicAttribute : Attribute { } 

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

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

  1. 挂钩到C#编译器,并在看到该属性时使其输出不同的代码。
    这看起来很不错,但现在根本不可能。 也许罗斯林项目未来可能允许这样做,但就目前而言,你不能这样做。

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

用于此的花哨的计算机科学术语是高阶编程

属性是元数据 – 这就是它们的全部

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

像PostSharp这样的AOP工具读取这样的元数据,以便知道将方面编织到代码中的内容和位置。

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

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

您需要做的是在应用程序启动时使用reflection来迭代程序集中每个类的每个方法,并找到用AtomicAttribute标记的方法,然后围绕该对象编写自定义代理。 然后以某种方式获取其他所有内容来调用您的代理而不是真正的实现,可能使用dependency injection框架。

大多数AOP框架在构建时执行此操作。 例如,PostSharp在VisualStudio构建程序集后运行。 它会扫描您的程序集并重写IL代码以包含代理和AOP拦截器。 这样,程序集在运行时都会设置为运行,但IL已经从您最初编写的内容发生了变化。

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

所以当你这样做时:

 var something = IoC.Resolve(); 

something不是你实现的对象而是代理。 在该代理中,您可以在方法调用之前和之后执行任何操作。