在C#中实现方法装饰器

10 投票
4 回答
6077 浏览
提问于 2025-04-17 18:35

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),请告诉我。

4 个回答

2

在C#中实现这种装饰器没有简单的方法——默认情况下,自定义属性只是用来描述的。不过,有一些项目可以扩展C#的编译器或运行时,让你真的能使用这些功能。我觉得最好的一个是PostSharp。使用它,你可以定义这种方法装饰器(通常称为“切面”),然后在编译时就能像你需要的那样包装这个方法。

我还见过通过装饰类来实际包装你的类来实现这个功能,但这工作量很大,而且我觉得很难做到真正通用。维基百科在装饰器模式的文章中展示了这一点。

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的面向切面编程。这篇文章能让你对这个概念有一个比较清晰的了解,以及它是如何工作的。

撰写回答