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

时间:2010-01-18 13:57:28

标签: .net asp.net attributes interface marker-interfaces

discussed before on Stack Overflow我们应该更喜欢属性marker interfaces(没有任何成员的接口)。 Interface Design article on MSDN也宣称这个建议:

  

避免使用标记接口(没有成员的接口)。

     

自定义属性提供了一种标记类型的方法。有关自定义属性的更多信息,请参阅编写自定义属性。如果可以在执行代码之前推迟检查属性,则首选自定义属性。如果您的方案需要编译时检查,则无法遵守此准则。

甚至还有FxCop rule来执行此建议:

  

避免空接口

     

接口定义提供行为或使用合同的成员。无论类型在继承层次结构中出现何种位置,接口描述的功能都可以采用任何类型。类型通过为接口的成员提供实现来实现接口。空接口不定义任何成员,因此,不定义可以实现的合同。

     

如果您的设计包含期望实现类型的空接口,则可能使用接口作为标记,或者标识一组类型的方法。如果此标识将在运行时发生,则完成此操作的正确方法是使用自定义属性。使用属性的存在或不存在或属性的属性来标识目标类型。如果标识必须在编译时进行,则可以使用空接口。

本文只说明了一个可能忽略警告的原因:何时需要编译时识别类型。 (这与界面设计文章一致)。

  

如果在编译时使用接口标识一组类型,则可以安全地从此规则中排除警告。

实际问题出在这里:Microsoft在框架类库的设计中(至少在几种情况下)不符合他们自己的建议:IRequiresSessionState interfaceIReadOnlySessionState interface。 ASP.NET框架使用这些接口来检查它是否应该为特定处理程序启用会话状态。显然,它不用于类型的编译时识别。他们为什么不这样做?我可以想到两个可能的原因:

  1. 微优化:检查对象是否实现接口(obj is IReadOnlySessionState)比使用反射检查属性(type.IsDefined(typeof(SessionStateAttribute), true))要快。大多数时候差异可以忽略不计,但它实际上可能对ASP.NET运行时中的性能关键代码路径很重要。但是,他们可以使用的解决方法就像为每个处理程序类型缓存结果一样。有趣的是,ASMX Web服务(具有类似性能特征)实际上使用EnableSession propertyWebMethod attribute来实现此目的。

  2. 与使用第三方.NET语言的属性装饰类型相比,实现接口的可能性更大。由于ASP.NET被设计为与语言无关,并且ASP.NET在CodeDom的帮助下生成类型的代码(可能是第三方语言),它们基于EnableSessionState实现所述接口<%@ Page %> directive的属性,使用接口而不是属性可能更有意义。

  3. 使用标记界面而不是属性的有说服力的理由是什么?

    这只是一个(过早?)优化还是框架设计中的一个小错误? (他们是否认为reflection is a "big monster with red eyes"?)想法?

5 个答案:

答案 0 :(得分:14)

我通常避免使用“标记接口”,因为它们不允许取消标记派生类型。但除此之外,以下是我看到的一些特定情况,其中标记接口比内置元数据支持更可取:

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

答案 1 :(得分:10)

对于泛型类型,您可能希望在标记接口中使用相同的泛型参数。这是属性无法实现的:

interface MyInterface<T> {}

class MyClass<T, U> : MyInterface<U> {}

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

这种界面可能有助于将类型与另一种类型相关联。

标记界面的另一个好用途是当您想要创建kind of mixin

interface MyMixin {}

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

class MyClass : MyMixin {
}

acyclic visitor pattern也使用它们。有时也使用术语“简并界面”。

<强>更新

我不知道这个是否重要,但是我已经用它来标记post-compiler的类来处理。

答案 2 :(得分:6)

微软在制作.NET 1.0时没有严格遵循这些指导原则,因为指南与框架一起发展,以及他们在更改API之前没有学到的一些规则。

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

Framework Design Guidelines中解释了这一点。


也就是说,该书还指出“[A]属性测试比类型检查要昂贵得多”(在Rico Mariani的附文中)。

接着说,有时您需要标记接口进行编译时检查,这对于属性是不可能的。但是,我发现书中给出的例子(第88页)令人难以置信,所以我不会在这里重复。

答案 3 :(得分:4)

我非常支持Marker Interfaces。我从不喜欢属性。我将它们视为类和成员的某种元信息,例如供调试者查看。与异常类似,他们不应该以我最谦卑的观点来影响正常的处理逻辑。

答案 4 :(得分:3)

从编码的角度来看,我认为我更喜欢标记界面语法,因为内置关键字asis。属性标记需要更多代码。

[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;
    }
}

使用代码的一些测试:

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);            
    }
}