使用标记接口而不是属性的令人信服的理由

在Stack Overflow之前我们已经讨论过 ,我们应该更喜欢属性到标记接口 (没有任何成员的接口)。 MSDN上的界面devise文章也强调了这个build议:

避免使用标记界面(没有成员的界面)。

自定义属性提供了一种标记types的方法。 有关自定义属性的更多信息,请参阅编写自定义属性。 自定义属性是首选的时候,你可以推迟检查的属性,直到代码执行。 如果你的情况需要编译时间检查,你不能遵守这个准则。

甚至有一个FxCop规则来执行这个build议:

避免空接口

接口定义提供行为或使用合同的成员。 接口所描述的function可以被任何types所采用,而不pipetypes在inheritance层次中出现的位置。 一个types通过为接口的成员提供实现来实现一个接口。 一个空的接口没有定义任何成员,因此,没有定义一个可以实现的合同。

如果您的devise包含types预期要实现的空接口,那么您可能使用接口作为标记,或者识别一组types。 如果此标识在运行时发生,则正确的方法是使用自定义属性。 使用属性的存在或不存在或属性的属性来标识目标types。 如果标识必须在编译时发生,那么使用空的接口是可以接受的。

本文只说明一个原因,您可能会忽略该警告:当您需要types的编译时间标识。 (这与界面devise文章一致)。

如果在编译时使用接口来标识一组types,那么从这个规则中排除警告是安全的。

下面是实际的问题:微软在框架类库的devise(至less在几个情况下)不符合他们自己的build议: IRequiresSessionState接口和IReadOnlySessionState接口 。 ASP.NET框架使用这些接口来检查是否应该为特定处理程序启用会话状态。 显然,它不用于types的编译时识别。 为什么他们不这样做? 我可以想到两个潜在的原因:

  1. 微优化:检查一个对象是否实现了一个接口( obj is IReadOnlySessionState )比使用reflection检查一个属性( type.IsDefined(typeof(SessionStateAttribute), true) )要快。 大部分时间差别可以忽略不计,但对于ASP.NET运行时中性能至关重要的代码path来说,这可能是非常重要的。 但是,他们可能使用的解决方法就像caching每个处理程序types的结果。 有趣的是,ASMX Web服务(具有类似的性能特征)实际上使用WebMethod属性的EnableSession属性来达到此目的。

  2. 实现接口的可能性比用第三方.NET语言装饰属性的types更有可能被支持。 由于ASP.NET被devise为与语言无关,并且ASP.NET根据<%@ Page %> EnableSessionState <%@ Page %>指令 ,使用接口而不是属性可能更有意义。

什么是使用标记接口而不是属性的说服力的原因?

这只是一个(过早的)优化或框架devise中的一个小错误? (他们认为reflection是“红眼睛的大怪物”吗?)思考?

我通常避免使用“标记接口”,因为它们不允许您取消派生types的标记 。 但是除此之外,下面是一些我已经看到的标记接口比内置的元数据支持更好的具体情况:

  1. 运行时性能敏感的情况。
  2. 与不支持注释或属性的语言兼容。
  3. 任何有兴趣的代码可能无法访问元数据的上下文。
  4. 支持通用约束和通用差异(通常是集合)。

对于genericstypes,您可能希望在标记接口中使用相同的generics参数。 这是不能通过属性来实现的:

 interface MyInterface<T> {} class MyClass<T, U> : MyInterface<U> {} class OtherClass<T, U> : MyInterface<IDictionary<U, T>> {} 

这种types的接口可能会有助于将一个types与另一个types关联。

标记接口的另一个好用处是当你想创build一种mixin时 :

 interface MyMixin {} static class MyMixinMethods { public static void Method(this MyMixin self) {} } class MyClass : MyMixin { } 

非循环的访客模式也使用它们。 术语“退化界面”有时也被使用。

更新:

我不知道这个是否重要,但是我用它们来标记一个后期编译器的类。

微软在制作.NET 1.0时并没有严格按照指导原则进行操作,因为这些指导方针与框架一起发展,还有一些规则,直到改变API为时已晚。

IIRC,你提到的例子属于BCL 1.0,所以可以解释它。

这在框架devise指南中进行了解释。


也就是说,这本书还指出:“属性testing比types检查成本高得多”(在Rico Mariani的边栏中)。

它继续说,有时你需要用于编译时检查的标记接口,这是不可能的一个属性。 但是,我发现书中给出的例子(第88页)没有说服力,所以在这里我不再重复。

我强烈赞成标记接口。 我从来不喜欢属性。 我把它们看作是一些类和成员的元信息,例如供debugging器看。 与例外类似,他们不应该影响正常的处理逻辑,以我最愚蠢的观点。

从编码的angular度来看,我认为我更喜欢标记接口语法,因为内置的关键字is 。 属性标记需要多一点的代码。

 [MarkedByAttribute] public class MarkedClass : IMarkByInterface { } public class MarkedByAttributeAttribute : Attribute { } public interface IMarkByInterface { } public static class AttributeExtension { public static bool HasAttibute<T>(this object obj) { var hasAttribute = Attribute.GetCustomAttribute(obj.GetType(), typeof(T)); return hasAttribute != null; } } 

还有一些testing使用代码:

 using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class ClassMarkingTests { private MarkedClass _markedClass; [TestInitialize] public void Init() { _markedClass = new MarkedClass(); } [TestMethod] public void TestClassAttributeMarking() { var hasMarkerAttribute = _markedClass.HasAttibute<MarkedByAttributeAttribute>(); Assert.IsTrue(hasMarkerAttribute); } [TestMethod] public void TestClassInterfaceMarking() { var hasMarkerInterface = _markedClass as IMarkByInterface; Assert.IsTrue(hasMarkerInterface != null); } }