为什么C#接口不能包含字段?

Why can't C# interfaces contain fields?

例如,假设我需要一个ICar接口,并且所有实现都将包含字段Year。这是否意味着每个实现都必须单独声明Year?在接口中简单地定义这个不是更好吗?


尽管在语义级别上许多其他答案都是正确的,但我发现从实现细节级别处理这些问题也很有趣。

一个接口可以被认为是包含方法的槽的集合。当一个类实现一个接口时,该类需要告诉运行时如何填充所有必需的槽。当你说

1
2
interface IFoo { void M(); }
class Foo : IFoo { public void M() { ... } }

这个类说"当你创建一个我的实例时,在ifoo.m的槽中填充一个对foo.m的引用。

当你打电话时:

1
2
IFoo ifoo = new Foo();
ifoo.M();

编译器生成的代码会说"询问对象ifoo.m槽中的方法是什么,并调用该方法"。

如果接口是包含方法的槽的集合,那么其中一些槽还可以包含属性的get和set方法、索引器的get和set方法以及事件的add和remove方法。但是字段不是一种方法。没有与字段关联的"槽",您可以使用字段位置的引用"填充"字段。因此,接口可以定义方法、属性、索引器和事件,但不能定义字段。


C中的接口旨在定义类将遵循的契约,而不是特定的实现。

本着这种精神,C接口的确允许定义属性——调用方必须为其提供实现:

1
2
3
4
interface ICar
{
    int Year { get; set; }
}

如果没有与属性关联的特殊逻辑,实现类可以使用自动属性来简化实现:

1
2
3
4
class Automobile : ICar
{
    public int Year { get; set; } // automatically implemented
}


声明为属性:

1
2
3
interface ICar {
   int Year { get; set; }
}


埃里克·利珀特明白了,我会用另一种方式来表达他的话。接口的所有成员都是虚拟的,它们都需要由继承接口的类重写。您没有在接口声明中显式地编写虚关键字,也没有在类中使用override关键字,它们是隐含的。

虚拟关键字是在.NET中用方法和所谓的v-table(方法指针数组)实现的。override关键字用不同的方法指针填充v-table槽,覆盖基类生成的方法指针。属性、事件和索引器作为方法在hood下实现。但事实并非如此。因此,接口不能包含字段。


为什么不拥有一个完美的Year财产呢?

接口不包含字段,因为字段表示数据表示的特定实现,公开它们会破坏封装。因此,拥有一个与字段的接口实际上就是编码到一个实现,而不是一个接口,这对于一个接口来说是一个奇怪的悖论!

例如,您的Year规范的一部分可能要求ICar实施者允许分配到比当前年份+1晚或1900之前的Year是无效的。如果您暴露了Year字段,就没有办法说明这一点——最好使用属性来代替这里的工作。


简短的回答是"是",每个实现类型都必须创建自己的支持变量。这是因为接口类似于契约。它所能做的就是指定实现类型必须提供的特定可公开访问的代码片段;它不能包含任何代码本身。

使用您的建议来考虑这个场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface InterfaceOne
{
    int myBackingVariable;

    int MyProperty { get { return myBackingVariable; } }
}

public interface InterfaceTwo
{
    int myBackingVariable;

    int MyProperty { get { return myBackingVariable; } }
}

public class MyClass : InterfaceOne, InterfaceTwo { }

我们这里有几个问题:

  • 由于接口的所有成员(根据定义)都是公共的,所以我们的支持变量现在对使用该接口的任何人都是公开的。
  • MyClass将使用哪个myBackingVariable

最常用的方法是声明接口和实现它的准抽象类。这允许您灵活地继承抽象类并免费获取实现,或者显式实现接口并允许从另一个类继承。它的工作原理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface IMyInterface
{
    int MyProperty { get; set; }
}

public abstract class MyInterfaceBase : IMyInterface
{
    int myProperty;

    public int MyProperty
    {
        get { return myProperty; }
        set { myProperty = value; }
    }
}


其他人已经给出了"为什么",所以我只想补充一点,您的接口可以定义一个控件;如果您将它包装在一个属性中:

1
2
3
4
5
6
7
8
public interface IView {
    Control Year { get; }
}


public Form : IView {
    public Control Year { get { return uxYear; } } //numeric text box or whatever
}

接口不包含任何实现。

  • 使用属性定义接口。
  • 此外,您可以在任何类中实现该接口,并继续使用这个类。
  • 如果需要,可以在类中将此属性定义为虚拟属性,以便修改其行为。

  • 已经说了很多了,但简单点说,这是我的看法。接口的目的是让方法契约由使用者或类实现,而不是让字段存储值。

    你可能会争辩说,那为什么允许属性?所以简单的答案是-属性在内部仅定义为方法。


    接口定义公共实例属性和方法。字段通常是私有的,或者最受保护的、内部的或受保护的内部的(术语"字段"通常不用于任何公共的内容)。

    如其他回复所述,您可以定义一个基类并定义一个受保护的属性,所有继承者都可以访问该属性。

    一个奇怪的地方是,一个接口实际上可以被定义为内部的,但是它限制了接口的有用性,并且它通常被用来定义其他外部代码不使用的内部功能。


    为此,您可以有一个实现Year字段的Car基类,并且所有其他实现都可以从中继承。