C# 标记界面的目的是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1023068/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:44:11  来源:igfitidea点击:

What is the purpose of a marker interface?

c#interfaceclass-design

提问by coder

What is the purpose of a marker interface?

标记界面的目的是什么?

回答by Mitch Wheat

Marker Interfaces are used to mark the capability of a class as implementing a specific interface at run-time.

标记接口用于将类的功能标记为在运行时实现特定接口。

The Interface Designand .NET Type Design Guidelines - Interface Designdiscourage the use of marker interfaces in favour of using attributes in C#, but as @Jay Bazuzi points out, it is easier to check for marker interfaces than for attributes: o is I

界面设计.NET类型设计准则-界面设计劝阻赞成在C#中使用属性的使用的标记接口,但作为@Jay Bazuzi指出,更容易检查比属性标记的接口:o is I

So instead of this:

所以而不是这个:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{
    ...
}

The .NET guidelines recommended that you do this:

.NET 指南建议您这样做:

public class FooAssignableAttribute : Attribute 
{
    ...
}

[FooAssignable]
public class Foo 
{    
   ...
} 

回答by Richard Anthony Hein

A marker interface is just an interface that is empty. A class would implement this interface as metadata to be used for some reason. In C# you would more commonly use attributes to mark up a class for the same reasons you'd use a marker interface in other languages.

标记接口只是一个空的接口。出于某种原因,类会将此接口实现为元数据。在 C# 中,出于在其他语言中使用标记接口的相同原因,您更常使用属性来标记类。

回答by Scott Wisniewski

This is a bit of a tangent based on the response by "Mitch Wheat".

基于“Mitch Wheat”的响应,这有点切线。

Generally, anytime I see people cite the framework design guidelines, I always like to mention that:

一般来说,每当我看到人们引用框架设计指南时,我总是喜欢提到:

You should generally ignore the framework design guidelines most of the time.

大多数情况下,您通常应该忽略框架设计指南。

This isn't because of any issue with the framework design guidelines. I think the .NET framework is a fantastic class library. A lot of that fantasticness flows from the framework design guidelines.

这不是因为框架设计指南有任何问题。我认为 .NET 框架是一个很棒的类库。很多奇妙之处都来自框架设计指南。

However, the design guidelines do not apply to most code written by most programmers. Their purpose is to enable the creation of a large framework that is used by millions of developers, not to make library writing more efficient.

但是,设计指南不适用于大多数程序员编写的大多数代码。它们的目的是创建一个供数百万开发人员使用的大型框架,而不是使库编写更高效。

A lot of the suggestions in it can guide you to do things that:

其中的许多建议可以指导您做以下事情:

  1. May not be the most straightforward way of implementing something
  2. May result in extra code duplication
  3. May have extra runtime overhead
  1. 可能不是实现某事的最直接方式
  2. 可能会导致额外的代码重复
  3. 可能有额外的运行时开销

The .net framework is big, really big. It's so big that it would be absolutely unreasonable to assume that anyone has detailed knowledge about every aspect of it. In fact, it's much safer to assume that most programmers frequently encounter portions of the framework they have never used before.

.net 框架很大,真的很大。它是如此之大,以至于假设任何人都对它的各个方面都有详细的了解是绝对不合理的。事实上,假设大多数程序员经常遇到他们以前从未使用过的框架部分会更安全。

In that case, the primary goals of an API designer are to:

在这种情况下,API 设计人员的主要目标是:

  1. Keep things consistent with the rest of the framework
  2. Eliminate unneeded complexity in the API surface area
  1. 保持与框架的其余部分一致
  2. 消除 API 表面区域中不必要的复杂性

The framework design guidelines push developers to create code that accomplishes those goals.

框架设计指南促使开发人员创建实现这些目标的代码。

That means doing things like avoiding layers of inheritance, even if it means duplicating code, or pushing all exception throwing code out to "entry points" rather than using shared helpers (so that stack traces make more sense in the debugger), and a lot of other similar things.

这意味着做一些事情,比如避免继承层,即使这意味着复制代码,或者将所有异常抛出代码推送到“入口点”而不是使用共享帮助程序(以便堆栈跟踪在调试器中更有意义),以及很多其他类似的事情。

The primary reason that those guidelines suggest using attributes instead of marker interfaces is because removing the marker interfaces makes the inheritance structure of the class library much more approachable. A class diagram with 30 types and 6 layers of inheritance hierarchy is very daunting compared to one with 15 types and 2 layers of hierarchy.

这些指南建议使用属性而不是标记接口的主要原因是因为删除标记接口使类库的继承结构更容易理解。与具有 15 种类型和 2 层层次结构的类图相比,具有 30 种类型和 6 层继承层次结构的类图非常令人生畏。

If there really are millions of developers using your APIs, or your code base is really big (say over 100K LOC) then following those guidelines can help a lot.

如果确实有数百万开发人员在使用您的 API,或者您的代码库非常大(比如超过 100K LOC),那么遵循这些指南会很有帮助。

If 5 million developers spend 15 mins learning an API rather than spending 60 mins learning it, the result is a net savings of 428 man years. That's a lot of time.

如果 500 万开发人员花 15 分钟学习 API,而不是花 60 分钟学习它,结果是净节省 428 人年。这是很多时间。

Most projects, however, don't involve millions of developers, or 100K+ LOC. In a typical project, with say 4 developers and around 50K loc, the set of assumptions are a lot different. The developers on the team will have a much better understanding of how the code works. That means that it makes a lot more sense to optimize for producing high quality code quickly, and for reducing the amount of bugs and the effort needed to make changes.

然而,大多数项目不涉及数百万开发人员或 100K+ LOC。在一个典型的项目中,假设有 4 个开发人员和大约 50K loc,假设集有很大不同。团队中的开发人员将对代码的工作方式有更好的理解。这意味着优化以快速生成高质量代码、减少错误数量和进行更改所需的工作更有意义。

Spending 1 week developing code that is consistent with the .net framework, vs 8 hours writing code that is easy to change and has fewer bugs can result in:

花 1 周时间开发与 .net 框架一致的代码,而 8 小时编写易于更改且错误较少的代码可能会导致:

  1. Late projects
  2. Lower bonuses
  3. Increased bug counts
  4. More time spent at the office, and less time on the beach drinking margaritas.
  1. 后期项目
  2. 较低的奖金
  3. 增加的错误数
  4. 更多的时间花在办公室,更少的时间在海滩上喝玛格丽塔酒。

Without 4,999,999 other developers to absorb the costs it usually isn't worth it.

如果没有 4,999,999 名其他开发商来承担成本,这通常是不值得的。

For example, testing for marker interfaces comes down to a single "is" expression, and results in less code that looking for attributes.

例如,对标记接口的测试归结为单个“is”表达式,并导致查找属性的代码更少。

So my advice is:

所以我的建议是:

  1. Follow the framework guidelines religiously if you are developing class libraries (or UI widgets) meant for wide spread consumption.
  2. Consider adopting some of them if you have over 100K LOC in your project
  3. Otherwise ignore them completely.
  1. 如果您正在开发旨在广泛使用的类库(或 UI 小部件),请严格遵循框架指南。
  2. 如果您的项目中有超过 10 万个 LOC,请考虑采用其中的一些
  3. 否则完全忽略它们。

回答by supercat

A marker interface allows a class to be tagged in a way that will be applied to all descendant classes. A "pure" marker interface wouldn't define or inherit anything; a more useful type of marker interfaces may be one which "inherits" another interface but defines no new members. For example, if there is an interface "IReadableFoo", one might also define an interface "IImmutableFoo", which would behave like a "Foo" but would promise anyone who uses it that nothing would change its value. A routine which accepts an IImmutableFoo would be able to use it as it would an IReadableFoo, but the routine would only accept classes that were declared as implementing IImmutableFoo.

标记接口允许以将应用于所有后代类的方式标记类。“纯”标记接口不会定义或继承任何东西;一种更有用的标记接口类型可能是“继承”另一个接口但不定义新成员的接口。例如,如果有一个接口“IReadableFoo”,我们也可以定义一个接口“IImmutableFoo”,它的行为就像一个“Foo”,但会向任何使用它的人保证它的值不会改变。接受 IImmutableFoo 的例程将能够像使用 IReadableFoo 一样使用它,但该例程只接受声明为实现 IImmutableFoo 的类。

I can't think of a whole lot of uses for "pure" marker interfaces. The only one I can think of would be if EqualityComparer(of T).Default would return Object.Equals for any type which implemented IDoNotUseEqualityComparer, even if the type also implemented IEqualityComparer. This would allow one to have an unsealed immutable type without violating the Liskov Substitution Principle: if the type seals all methods related to equality-testing, a derived type could add additional fields and have them be mutable, but the mutation of such fields wouldn't be visible using any base-type methods. It might not be horrible to have an unsealed immutable class and either avoid any use of EqualityComparer.Default or trust derived classes not to implement IEqualityComparer, but a derived class which did implement IEqualityComparer could appear as a mutable class even when viewed as a base-class object.

我想不出“纯”标记接口有很多用途。我唯一能想到的是,如果 EqualityComparer(of T).Default 会为实现 IDoNotUseEqualityComparer 的任何类型返回 Object.Equals,即使该类型也实现了 IEqualityComparer。这将允许在不违反 Liskov 替换原则的情况下拥有未密封的不可变类型:如果该类型密封所有与相等性测试相关的方法,则派生类型可以添加额外的字段并使它们可变,但此类字段的突变不会“使用任何基本类型的方法都可见。拥有一个未密封的不可变类并避免使用 EqualityComparer.Default 或信任派生类不实现 IEqualityComparer,这可能并不可怕,

回答by Tom B

Since every other answer has stated "they should be avoided", it would be useful to have an explanation of why.

由于所有其他答案都表示“应该避免它们”,因此解释原因会很有用。

Firstly, why marker interfaces are used: They exist to allow the code that's using the object that implements it to check whether they implement said interface and treat the object differently if it does.

首先,为什么要使用标记接口:它们的存在是为了允许使用实现它的对象的代码检查它们是否实现了所述接口,如果实现了,则以不同的方式对待该对象。

The problem with this approach is that it breaks encapsulation. The object itself now has indirect control over how it will be used externally. Moreover, it has knowledge of the system it's going to be used in. By applying the marker interface, the class definition is suggesting it expects to be used somewhere that checks for the existence of the marker. It has implicit knowledge of the environment it's used in and is trying to define how it should be being used. This goes against the idea of encapsulation because it has knowledge of the implementation of a part of the system that exists entirely outside its own scope.

这种方法的问题在于它破坏了封装。对象本身现在可以间接控制它在外部的使用方式。此外,它了解将要使用的系统。通过应用标记接口,类定义表明它希望在检查标记存在的地方使用。它对它所使用的环境有隐含的知识,并试图定义它应该如何使用。这与封装的想法背道而驰,因为它了解完全存在于其自身范围之外的系统部分的实现。

At a practical level this reduces portability and reusability. If the class is re-used in a different application, the interface needs to be copied across too, and it may not have any meaning in the new environment, making it entirely redundant.

在实践层面,这会降低可移植性和可重用性。如果该类在不同的应用程序中重复使用,则接口也需要复制,并且它在新环境中可能没有任何意义,使其完全多余。

As such, the "marker" is metadata about the class. This metadata is not used by the class itself and is only meaningful to (some!) external client code so that it can treat the object in a certain manner. Because it only has meaning to the client code, the metadata should be in the client code, not the class API.

因此,“标记”是关于类的元数据。类本身不使用此元数据,仅对(某些!)外部客户端代码有意义,以便它可以以某种方式处理对象。因为它只对客户端代码有意义,所以元数据应该在客户端代码中,而不是类 API。

The difference between a "marker interface" and a normal interface is that an interface with methods tells the outside world how it canbe used whereas an empty interface implies it's telling the outside world how it shouldbe used.

一个“标记接口”和普通接口之间的区别是,方法的接口告诉外面的世界怎么可以使用,而空的接口意味着它告诉外面的世界如何应该被使用。

回答by Marc Sigrist

Marker interfaces may sometimes be a necessary evil when a language does not support discriminated uniontypes.

当语言不支持可区分的联合类型时,标记接口有时可能是必要的邪恶。

Suppose you want to define a method who expects an argument whose type must be exactly one of A, B, or C. In many functional-first languages (like F#), such a type can be cleanly defined as:

假设您要定义一个方法,该方法需要一个类型必须恰好是 A、B 或 C 之一的参数。在许多功能优先语言(如F#)中,这样的类型可以明确定义为:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C

However, in OO-first languages such as C#, this is not possible. The only way to achieve something similar here is to define interface IArg and "mark" A, B and C with it.

但是,在面向对象的语言(例如 C#)中,这是不可能的。在这里实现类似的唯一方法是定义接口 IArg 并用它“标记”A、B 和 C。

Of course, you could avoid using the marker interface by simply accepting type "object" as argument, but then you would lose expressiveness and some degree of type safety.

当然,您可以通过简单地接受类型“对象”作为参数来避免使用标记接口,但是这样您将失去表达能力和某种程度的类型安全性。

Discriminated union types are extremely useful and have existed in functional languages for at least 30 years. Strangely, to this day, all mainstream OO languages have ignored this feature -- although it has actually nothing to do with functional programming per se, but belongs to the type system.

可区分联合类型非常有用,并且在函数式语言中已经存在至少 30 年了。奇怪的是,直到今天,所有主流 OO 语言都忽略了这个特性——虽然它实际上与函数式编程本身无关,但属于类型系统。

回答by Mr Anderson

These two extension methods will solve most of the issues Scott asserts favor marker interfaces over attributes:

这两种扩展方法将解决 Scott 断言支持标记接口而不是属性的大部分问题:

public static bool HasAttribute<T>(this ICustomAttributeProvider self)
    where T : Attribute
{
    return self.GetCustomAttributes(true).Any(o => o is T);
}

public static bool HasAttribute<T>(this object self)
    where T : Attribute
{
    return self != null && self.GetType().HasAttribute<T>()
}

Now you have:

现在你有:

if (o.HasAttribute<FooAssignableAttribute>())
{
    //...
}

versus:

相对:

if (o is IFooAssignable)
{
    //...
}

I fail to see how building an API will take 5 times as long with the first pattern compared to the second, as Scott claims.

正如 Scott 声称的那样,我不明白第一种模式与第二种模式相比,构建 API 所需的时间是后者的 5 倍。

回答by Rick O'Shea

Markers are empty interfaces. A marker is either there or it isn't.

标记是空接口。标记存在或不存在。

class Foo : IConfidential

类 Foo : IConfidential

Here we mark Foo as being confidential. No actual additional properties or attributes required.

在这里,我们将 Foo 标记为机密。不需要实际的附加属性或属性。

回答by Arun Raaj

Marker interface is a total blank interface that has no body/data-members/implementation.
A class implementsmarker interface when required, it is just to "mark"; means it tells the JVM that the particular class is for the purpose of cloning so allow it to clone. This particular class is to Serialize its objects so please allow its objects to get serialized.

标记接口是一个完全空白的接口,没有正文/数据成员/实现。
一个类在需要的时候实现marker接口,只是为了“ mark”;意味着它告诉 JVM 特定的类是为了克隆,所以允许它克隆。这个特殊的类是序列化它的对象,所以请允许它的对象被序列化。

回答by Ali Bayat

The marker interface is really just a procedural programming in an OO language. An interface defines a contract between implementers and consumers, except a marker interface, because a marker interface defines nothing but itself. So, right out of the gate, the marker interface fails at the basic purpose of being an interface.

标记接口实际上只是一种面向对象语言的过程编程。接口定义了实现者和消费者之间的契约,标记接口除外,因为标记接口只定义了自身。因此,一开始,标记接口就无法实现作为接口的基本目的。