在C#中实现方法装饰器

10
在Python中,可以使用函数装饰器来扩展函数和方法的行为。特别是我正在将一个设备库从Python迁移到C#。与设备进行通信可能会产生错误,应该使用自定义异常重新引发它们。在Python中,我会这样写:
@device_error_wrapper("Device A", "Error while setting output voltage.")   
def set_voltage(self, voltage):
    """
    Safely set the output voltage of device.
    """
    self.__handle.write(":source:voltage:level {0}".format(voltage))
该方法调用将扩展为
try:
    self.__handle.write(":source:voltage:level {0}".format(voltage))
except Error:
    raise DeviceError("Error while setting output voltage.", "DeviceA")

有了这个模式,你可以轻松地包装和扩展方法,而无需在每个方法中编写每个try-except子句。

是否可以使用C#实现类似的模式?

如果需要实现装饰器(device_error_wrapper),请告诉我。


我知道的一种替代方法是使用委托(示例在此答案中https://dev59.com/Zms05IYBdhLWcg3wNPI3),或者您可以尝试使用PostSharp(也在其中一个答案中提到)。如果真的可以使用属性作为方法装饰器来实现这个目的,我不确定(我怀疑但我敢打赌有人会回答你的问题)。 - bas
4个回答

8

正如其他人指出的那样,像PostSharp这样的工具可以让您在编译期间(实际上是在编译之后)编织交叉逻辑。

另一种选择是在运行时完成。一些IoC工具允许您定义拦截器,然后将其添加到代理类中以实现您的实现。这听起来比实际情况复杂得多,所以我将基于Castle DynamicProxy给出一个示例。

首先,您定义需要包装的类。

[Interceptor(typeof(SecurityInterceptor))]
public class OrderManagementService : IOrderManagementService
{
    [RequiredPermission(Permissions.CanCreateOrder)]
    public virtual Guid CreateOrder(string orderCode)
    {   
        Order order = new Order(orderCode);
        order.Save(order); // ActiveRecord-like implementation
        return order.Id;
    }
} 

RequiredPermission 在这里作为一个装饰器。该类本身被 Interceptor 属性装饰,指定接口方法调用的处理程序。这也可以放在配置中,因此不会显示在类中。

拦截器实现包含装饰器逻辑。

class SecurityInterceptor : IMethodInterceptor
{
    public object Intercept(IMethodInvocation invocation, params object[] args)
    {
        MethodInfo method = invocation.Method;
        if (method.IsDefined(typeof(RequiredPermission), true) // method has RequiredPermission attribute
            && GetRequiredPermission(method) != Context.Caller.Permission) {
            throw new SecurityException("No permission!");  
        }

        return invocation.Proceed(args);
    }

    private Permission GetRequiredPermission(MethodInfo method)
    {
         RequiredPermission attribute = (RequiredPermission)method.GetCustomAttributes(typeof(RequiredPermission), false)[0];
        return attribute.Permission;
    }
} 

然而,也存在一些缺点:

  • 使用DynamicProxy只能包装接口和虚方法。
  • 需要通过IoC容器来实例化对象,而不能直接实例化(如果您已经使用IoC容器,则不是问题)。

5
您可以使用面向方面编程来实现类似的功能。我过去只用过PostSharp,但商业用途需要付费。
还有其他AOP解决方案,您也可以使用Mono.Cecil来实现类似的功能,但需要更多工作量。
Reza Ahmadi撰写了一篇很好的简介文章,名为使用C#和PostSharp的面向方面编程。它可以让您清楚地了解预期效果和工作原理。

关于Mono.Cecil的内容。你可以看一下Simon Cropp的Fody。它是一个不错的插件,可以帮助你将预先构建好的方面织入到你的代码中。它还包含一个模板,可以帮助你构建自己的方面。 - Aron

2

在C#中,实现这样的装饰器没有简单的方法 - 默认情况下,自定义属性只是描述性的。但是,有一些项目扩展了C#编译器或运行时,以便您可以真正使用它们。我认为最好的一个是PostSharp。使用它,您可以定义这种方法装饰器(“方面”通常),并且方法在编译期间像您需要的那样进行包装。

我也看到过通过用装饰器类装饰你的类来实现这一点,但那是很多工作,而且我认为它无法以真正通用的方式完成。维基百科在装饰器模式文章中展示了这一点。


1

如其他人所提到的,您正在寻找AOP。PostSharp是一个很好的后编译解决方案,但Castle DynamicProxy是一个运行时AOP解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接