![](/img/trans.png)
[英]Use Attributes to add decoration code to property's set/get method
[英]Can attributes be used to modify a method's code/behavior?
假设您有以下形式的几种方法(出于说明目的,此处已大大简化):
片段 1
public async Task<T> DoSomething<T>()
{
return await MyApi.DoSomeOperation<T>();
}
您希望为所有此类方法添加异常处理功能,但无需编写大量重复代码。 最后,您的方法应该看起来(或执行)如下所示:
片段 2
public async Task<T> DoSomething<T>()
{
try
{
return await MyApi.DoSomeOperation<T>();
}
catch (Exception ex)
{
var apiEx = new MyApiException(ex);
MyLogger.Log(apiEx);
throw apiEx;
}
}
有没有办法用属性做到这一点? 例如,我可以做这样的事情吗?
片段 3
public class TryCatchLogThrowAttribute : Attribute
{
public Type ExceptionType { get; set; }
public Type LoggerType { get; set; }
// logic goes here:
}
然后按如下方式装饰我的方法(期望实际执行的代码看起来或行为类似于上面的代码段 2)?
片段 4
[TryCatchLogThrow(typeof(MyApiException), typeof(MyLogger))]
public async Task<T> DoSomething<T>()
{
return await MyApi.DoSomeOperation<T>();
}
这有多可行?
简单的答案是:不。C# 和 .NET 都不支持开箱即用。 属性是附加到成员或类型的元数据,但必须由代码拾取和使用它们才能执行任何操作。
一些属性直接影响编译器,一些实际上并不作为属性存储,而是影响元数据标志,其余的只是作为元数据与类型的每个成员一起存储。
在几乎所有情况下,开箱即用,它们只能通过反射获得,因此您必须编写额外的代码来检查它们的存在并采取行动。 然而,成员本身并没有注意到这些属性的存在,并且内部的代码不会因此而改变。
您所描述的是 AOP,面向方面的编程,您将方面(通常是横切行为)附加到类型或成员,例如日志记录、性能监控、错误处理、访问控制等,而无需实际将行为写入成员作为可读代码。
开箱即用不支持此功能,但有一些产品和库可让您执行此操作。
我可以列出一些,但这个列表并不详尽,因为我已经有几年没有研究 AOP了:
这两种方法都允许您(无论是否为您自动化)加载已编译的程序集,解码并拆开它,在某些地方注入额外的代码,然后将重写的程序集写入磁盘。
在这两者中,我强烈推荐 PostSharp,因为它是迄今为止两者中最容易使用的,但我再次关注 AOP 已经很长时间了,所以可能会有更多的竞争者来争夺这个宝座。
您正在寻找一种实现面向方面编程的方法。 一个有价值的目标。
有没有办法用属性做到这一点?
如果您想使用属性来实现这一点,则只能使用特殊工具来完成。 有两种类型可供选择:
这两种选择都有各自的后果:
然而,还有第三种选择,它不需要工具,即使用装饰器设计模式。 不过,它确实需要使用接口。
将您的DoSomething<T>()
方法视为接口的一部分:
public interface IDoStuff
{
Task<T> DoSomething<T>();
}
您的默认实现具有您问题的原始代码:
public class DefaultDoStuff : IDoStuff
{
public async Task<T> DoSomething<T>()
{
return await MyApi.DoSomeOperation<T>();
}
}
现在,您可以通过创建一个允许包装原始IDoStuff
的新IDoStuff
实现来扩展DefaultDoStuff
的行为,如下所示:
public class ExceptionLoggingDoStuff : IDoStuff
{
private readonly ILogger logger;
private readonly IDoStuff original;
public ExceptionLoggingDoStuff(ILogger logger, IDoStuff original)
{
this.logger = logger;
this.original = original;
}
public async Task<T> DoSomething<T>()
{
try
{
return await this.original.DoSomething<T>();
}
catch (Exception ex)
{
var apiEx = new MyApiException(ex);
this.logger.Log(apiEx);
throw apiEx;
}
}
}
现在,使用第二个ExceptionLoggingDoStuff
实现,您可以构造以下对象图:
// Construct objects
IDoStuff stuff =
new ExceptionLoggingDoStuff(
new MyLogger(),
new DefaultDoStuff());
// Use it
stuff.DoSomething<object>();
ExceptionLoggingDoStuff
是一个装饰器,因为它允许装饰(或包装)另一个IDoStuff
。 这允许您扩展DefaultDoStuff
的行为而无需更改它。
装饰器可以非常强大,要以防止代码重复的方式使用它们,它需要一个非常特别设计的应用程序,即遵循SOLID 原则的设计; 这并不总是容易实现的。 在我们的书的第 10 章中,Mark Seemann 和我将这种技术称为“面向方面的设计编程”。 因为它试图仅使用与使用工具相反的软件设计原则和模式来实现 AOP。 我们书的第 11 章描述了这种基于工具的 AOP 方法并讨论了它的缺点(在 DI 和松散耦合的背景下)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.