使用标记接口而不是属性的令人信服的理由
之前在 Stack Overflow 上讨论过我们应该更喜欢属性而不是标记接口(没有任何成员的接口)。 MSDN 上的界面设计文章也提出了此建议:
避免使用标记接口(没有成员的接口)。
自定义属性提供了一种标记类型的方法。有关自定义属性的更多信息,请参阅编写自定义属性。当您可以将属性检查推迟到代码执行时,自定义属性是首选。如果您的场景需要编译时检查,则无法遵守此准则。
甚至还有一个 FxCop 规则来强制执行此建议:
避免空接口
接口定义提供行为或使用契约的成员。接口描述的功能可以被任何类型采用,无论该类型出现在继承层次结构中的哪个位置。类型通过提供接口成员的实现来实现接口。空接口不定义任何成员,因此不定义可以实现的契约。
如果您的设计包含预期类型要实现的空接口,则您可能正在使用接口作为标记,或者识别一组类型的方法。如果此识别将在运行时发生,则完成此操作的正确方法是使用自定义属性。使用属性的存在或不存在或属性的特性来识别目标类型。如果必须在编译时进行识别,则可以使用空接口。
本文只说明了您可能会忽略该警告的一个原因:当您需要类型的编译时识别时。 (这与界面设计文章一致)。
如果接口用于在编译时识别一组类型,则可以安全地从该规则中排除警告。
真正的问题来了:微软在框架类库的设计中没有遵循他们自己的建议(至少在一些情况下):IRequiresSessionState 接口 和 IReadOnlySessionState 接口。 ASP.NET 框架使用这些接口来检查是否应该为特定处理程序启用会话状态。显然,它不用于编译时类型识别。为什么他们不这样做?我可以想到两个潜在的原因:
微优化:检查对象是否实现接口(
obj is IReadOnlySessionState
)比使用反射检查属性(type. IsDefined(typeof(SessionStateAttribute), true)
)。大多数情况下,这种差异可以忽略不计,但对于 ASP.NET 运行时中的性能关键代码路径来说,它实际上可能很重要。但是,他们可以使用一些解决方法,例如缓存每种处理程序类型的结果。有趣的是,ASMX Web 服务(具有类似的性能特征)实际上使用WebMethod
属性 用于此目的。第三方 .NET 语言实现接口可能比使用属性修饰类型更受支持。由于 ASP.NET 被设计为与语言无关,并且 ASP.NET 生成类型代码(可能在 CodeDom),它基于
<%@ Page %>
指令,使用接口而不是属性可能更有意义.
使用标记接口而不是属性的有说服力的理由是什么?
这只是一个(过早的?)优化还是框架设计中的一个小错误? (他们是否认为反射是一个“红眼睛的大怪物”?)想法?
It's been discussed before on Stack Overflow that we should prefer attributes to marker interfaces (interfaces without any members). Interface Design article on MSDN asserts this recommendation too:
Avoid using marker interfaces (interfaces with no members).
Custom attributes provide a way to mark a type. For more information about custom attributes, see Writing Custom Attributes. Custom attributes are preferred when you can defer checking for the attribute until the code is executing. If your scenario requires compile-time checking, you cannot comply with this guideline.
There's even an FxCop rule to enforce this recommendation:
Avoid empty interfaces
Interfaces define members that provide a behavior or usage contract. The functionality described by the interface can be adopted by any type, regardless of where the type appears in the inheritance hierarchy. A type implements an interface by providing implementations for the interface's members. An empty interface does not define any members, and as such, does not define a contract that can be implemented.
If your design includes empty interfaces that types are expected to implement, you are probably using an interface as a marker, or a way of identifying a group of types. If this identification will occur at runtime, the correct way to accomplish this is to use a custom attribute. Use the presence or absence of the attribute, or the attribute's properties, to identify the target types. If the identification must occur at compile time, then using an empty interface is acceptable.
The article states only one reason that you might ignore the warning: when you need compile time identification for types. (This is consistent with the Interface Design article).
It is safe to exclude a warning from this rule if the interface is used to identify a set of types at compile-time.
Here comes the actual question: Microsoft didn't conform to their own recommendation in the design of the Framework Class Library (at least in a couple cases): IRequiresSessionState interface and IReadOnlySessionState interface. These interfaces are used by the ASP.NET framework to check whether it should enable session state for a specific handler or not. Obviously, it's not used for compile time identification of types. Why they didn't do that? I can think of two potential reasons:
Micro-optimization: Checking whether an object implements an interface (
obj is IReadOnlySessionState
) is faster than using reflection to check for an attribute (type.IsDefined(typeof(SessionStateAttribute), true)
). The difference is negligible most of the time but it might actually matter for a performance-critical code path in the ASP.NET runtime. However, there are workarounds they could have used like caching the result for each handler type. The interesting thing is that ASMX Web services (which are subject to similar performance characteristics) actually use theEnableSession
property of theWebMethod
attribute for this purpose.Implementing interfaces are potentially more likely to be supported than decorating types with attributes by third-party .NET languages. Since ASP.NET is designed to be language agnostic, and ASP.NET generates code for types (possibly in a third-party language with the help of CodeDom) that implement the said interfaces based on the
EnableSessionState
attribute of the<%@ Page %>
directive, it might make more sense to use interfaces instead of attributes.
What are the persuasive reasons to use marker interfaces instead of attributes?
Is this simply a (premature?) optimization or a tiny mistake in the framework design? (Do they think reflection is a "big monster with red eyes"?) Thoughts?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我通常会避免使用“标记接口”,因为它们不允许您取消标记派生类型。但除此之外,以下是我见过的一些特定情况,其中标记接口比内置元数据支持更可取:
I generally avoid "marker interfaces" because they don't allow you to unmark a derived type. But that aside, here are some of the specific cases that I have seen where marker interfaces would be preferable to built-in meta-data support:
对于泛型类型,您可能希望在标记接口中使用相同的泛型参数。这是通过属性无法实现的:
这种接口对于将一种类型与另一种类型相关联可能很有用。
标记界面的另一个很好的用途是当您想要创建 种类时mixin:
非循环访问者模式也使用它们。有时也使用术语“简并接口”。
更新:
我不知道这是否重要,但我已经用它们来标记 后编译器 来工作。
For a generic type you might want to use the same generic parameter in a marker interface. This is not achievable by an attribute:
This kind of interface might be useful to associate a type with another one.
Another good use for a marker interface is when you want to create a kind of mixin:
The acyclic visitor pattern also uses them. The term "degenerate interface" is sometimes used as well.
UPDATE:
I don't know if this one counts, but I've used them to mark classes for a post-compiler to work on.
微软在制作.NET 1.0时并没有严格遵循准则,因为准则是随着框架一起演变的,有些规则他们直到更改API时才了解到,为时已晚。
IIRC,你提到的例子属于BCL 1.0,这样就可以解释了。
框架设计指南对此进行了解释。
也就是说,该书还指出“属性测试比类型检查的成本要高得多”(在 Rico Mariani 的侧边栏中)。
它接着说,有时您需要标记接口来进行编译时检查,而这对于属性来说是不可能的。不过,我觉得书中给出的例子(第88页)缺乏说服力,所以这里不再重复。
Microsoft didn't strictly follow the guidelines when they made .NET 1.0, because the guidelines evolved together with the framework, and some of the rules they didn't learn until it was too late to change the API.
IIRC, the examples you mention belong to BCL 1.0, so that would explain it.
This is explained in Framework Design Guidelines.
That said, the book also remarks that "[A]ttribute testing is a lot more costly than type checking" (in a sidebar by Rico Mariani).
It goes on to say that sometimes you need the marker interface for compile time checking, which isn't possible with an attribute. However, I find the example given in the book (p. 88) unconvincing, so I will not repeat it here.
我非常支持标记接口。我从来不喜欢属性。我将它们视为类和成员的某种元信息,供调试器查看。与异常类似,在我看来,它们不应该影响正常的处理逻辑。
I'm strongly pro Marker Interfaces. I never liked Attributes. I see them as some kind of meta information for classes and members intended for example for debuggers to look at. Similar to Exceptions, they should not influence normal processing logic, in my most humble opinion.
从性能角度来看:
由于反射,标记属性将比标记接口慢。如果您不缓存反射,则始终调用
GetCustomAttributes
可能会成为性能瓶颈。我之前对此进行了基准测试,即使在使用缓存反射时,使用标记接口在性能方面也能获胜。这只适用于在经常调用的代码中使用它的情况。
但这并没有显着差异。
From performance point of view:
Marker attributes will be slower than marker interfaces because of reflection. If you don't cache reflection then calling
GetCustomAttributes
all the time can be a performance bottleneck. I benchmarked this before and using marker interfaces win in terms of performance even when using cached reflection.This only applies when you use it in the code that's frequently called.
It's not significant difference though.
从编码的角度来看,我认为我更喜欢标记接口语法,因为内置关键字
as
和is
。属性标记需要更多的代码。以及使用代码的一些测试:
From a coding point of view, I think I prefer the Marker Interface syntax because of the built in keywords
as
andis
. Attribute marking requires a little bit more code.And some tests to use the code: