之前在 Stack Overflow 上讨论过,我们应该更喜欢属性而不是标记接口(没有任何成员的接口)。MSDN 上的界面设计文章也提出了这个建议:
避免使用标记接口(没有成员的接口)。
自定义属性提供了一种标记类型的方法。有关自定义属性的更多信息,请参阅编写自定义属性。当您可以将属性检查推迟到代码执行时,自定义属性是首选。如果您的方案需要编译时检查,则您不能遵守此指南。
甚至还有一个FxCop 规则来强制执行此建议:
避免空接口
接口定义了提供行为或使用契约的成员。接口描述的功能可以被任何类型采用,无论该类型出现在继承层次结构中的什么位置。类型通过为接口的成员提供实现来实现接口。空接口没有定义任何成员,因此也没有定义可以实现的合约。
如果您的设计包含类型预期实现的空接口,则您可能正在使用接口作为标记,或者是标识一组类型的一种方式。如果此标识将在运行时发生,则完成此操作的正确方法是使用自定义属性。使用属性的存在与否或属性的属性来识别目标类型。如果必须在编译时进行识别,那么使用空接口是可以接受的。
该文章仅说明了您可能会忽略警告的一个原因:当您需要类型的编译时标识时。(这与界面设计文章一致)。
如果接口用于在编译时识别一组类型,则从该规则中排除警告是安全的。
真正的问题来了:微软在框架类库的设计中没有遵循他们自己的建议(至少在几个情况下):IRequiresSessionState 接口和IReadOnlySessionState 接口。ASP.NET 框架使用这些接口来检查它是否应该为特定处理程序启用会话状态。显然,它不用于类型的编译时识别。为什么他们不这样做?我可以想到两个潜在的原因:
微优化:检查对象是否实现接口(
obj is IReadOnlySessionState
)比使用反射检查属性(type.IsDefined(typeof(SessionStateAttribute), true)
)更快。大多数情况下,这种差异可以忽略不计,但它实际上可能对 ASP.NET 运行时中的性能关键代码路径很重要。但是,他们可以使用一些变通方法,例如为每个处理程序类型缓存结果。有趣的是,ASMX Web 服务(具有相似的性能特征)实际上为此目的使用了EnableSession
属性的WebMethod
属性。与使用第三方 .NET 语言的属性修饰类型相比,实现接口可能更受支持。由于 ASP.NET 被设计为与语言无关,并且 ASP.NET 为基于指令属性实现所述接口的类型(可能在CodeDom的帮助下使用第三方语言)生成代码,它可能会产生更多感觉使用接口而不是属性。
EnableSessionState
<%@ Page %>
使用标记接口而不是属性的有说服力的理由是什么?
这仅仅是一个(过早的?)优化还是框架设计中的一个小错误?(他们认为反射是一个“红眼睛的大怪物”吗?)想法?