我想实现这样的功能:
[Atomic]
public void Foo()
{
/* foo logic */
}
其中[Atomic]
属性是一个属性,它在事务范围内包装函数逻辑:
using(var scope = new TransactionScope())
{
/* foo logic */
scope.Complete();
}
如何写这样的属性?
我之前问过基本相同的question,我知道这可以用AOP完成,但我没有提到我正在寻找一些最简单的概念证明实现或者有用的文章可以帮助我使用纯.NET Framework编写它(我想使用RealProxy
和MarshalByRefObject
类型,我已经阅读了相关的问题。
我需要完全解决这个显示的例子。这似乎是一个基本的东西,所以我想从头开始学习如何做到这一点。它现在不需要安全和灵活。
答案 0 :(得分:23)
这似乎是一件基本的事情......
这是(很多)很容易理解这个概念的东西之一,但实现起来并不简单。
根据Oded's answer,.NET中的属性不会执行任何操作。它们只存在,以便其他代码(或开发人员)可以在以后查看它们。把它想象成一个花哨的评论。
考虑到这一点,你可以像这样编写你的属性
public class AtomicAttribute : Attribute { }
现在很难,您必须编写一些代码来扫描该属性,并更改代码的行为。
鉴于C#是一种编译语言,并且根据.NET CLR的规则理论上有3种方法可以做到这一点
挂钩进入C#编译器,并在看到该属性时输出不同的代码 这看起来很不错,但根本不可能 马上。也许是 Roslyn 项目未来可能允许这样做,但就目前而言,你不能这样做。
在 C#编译器将其转换为MSIL之后编写将扫描.NET程序集的内容,然后更改MSIL。
这基本上是PostSharp的作用。扫描和重写MSIL hard 。像Mono.Cecil这样的库可以提供帮助,但它仍然是一个非常困难的问题。它也可能会干扰调试器等。
使用.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
}
}
花哨的计算机科学术语是Higher order programming
答案 1 :(得分:10)
属性是元数据 - 它们是所有。
有许多工具可以利用此类元数据,但此类工具需要了解该属性。
像PostSharp这样的AOP工具会读取此类元数据,以便了解将方面编织到代码中的位置和位置。
简而言之 - 只需编写AtomicAttribute
即可为您提供 - 您需要通过知道此属性的工具传递已编译的程序集并对其执行“某些操作”为了实现AOP。
答案 2 :(得分:4)
这根本不是一件基本的事情。没有额外的代码只是因为方法具有属性而运行,因此无法放置TransactionScope
代码。
您需要做的是在应用程序启动时使用反射来迭代程序集中每个类的每个方法,并找到标有AtomicAttribute
的方法,然后围绕该对象编写自定义代理。然后以某种方式获取其他所有内容来调用代理而不是真正的实现,可能使用依赖注入框架。
大多数AOP框架在构建时执行此操作。例如,PostSharp在VisualStudio构建程序集后运行。它会扫描您的程序集并重写IL代码以包含代理和AOP拦截器。这样,程序集在运行时都会设置为运行,但IL已经从您最初编写的内容发生了变化。
答案 3 :(得分:3)
也许使用IoC容器解析所有对象? 您可以为类型配置拦截器,并在其中检查是否使用该属性修饰了被调用的方法。您可以缓存该信息,这样就不必在每次方法调用时都使用反射。
所以当你这样做时:
var something = IoC.Resolve<ISomething>();
something
不是您实现的对象,而是代理。在该代理中,您可以在方法调用之前和之后执行任何操作。