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


223

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


21
接口没有实现,为此使用抽象类,其属性为Year
PostMan 2010年

6
补充说一下,接口是契约,而字段是实现细节,因为它定义了机器内存中的一个插槽,用于将值(标量或地址指针)放入其中。
herzmeister

5
但是,如果该领域是公共领域,则它是合同的一部分,而不仅仅是实施细节,对吗?
Alex

Answers:


238

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

接口可以看作是插槽的集合,其中包含方法。当类实现接口时,需要该类告诉运行时如何填写所有必需的插槽。当你说

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

该类说:“当您创建我的实例时,请在IFoo.M的插槽中填充对Foo.M的引用。

然后,当您拨打电话时:

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

编译器生成的代码表明“向对象询问IFoo.M插槽中的方法是什么,然后调用该方法。

如果接口是包含方法的插槽的集合,那么这些插槽中的某些插槽也可以包含属性的get和set方法,索引器的get和set方法以及事件的add和remove方法。但是领域不是方法。没有与该字段关联的“插槽”,然后您可以通过引用该字段位置来“填写”。因此,接口可以定义方法,属性,索引器和事件,但不能定义字段。


26
我有时会错过的一件事是定义接口级常量的类Java能力,该常量可能不需要语言中的“槽”即可支持。
LBushkin

2
我喜欢简单的解释。谢谢。“通过C#进行CLR”和“基本.net卷1”提供了更多详细信息。
Sandeep GB

6
为什么一个字段没有插槽?和运营商有同样的问题吗?我记得曾经听说过使用反射来进行鸭子输入,以查看是否实现了接口,即使该类未继承该接口也是如此。为什么不能使用反射(或缝隙)来拉起电场?我仍在编写代码,因此我可能不需要/想要输入字段,但是我很惊讶地发现我无法使用运算符。运营商是完全一样,除了不是所有可以被重载从我的理解方法(An interface cannot contain constants, fields, operators从。msdn.microsoft.com/en-us/library/ms173156.aspx

@acidzombie:为什么一个接口不能定义运算符的问题与为什么它不能包含字段是不同的(尽管可能是相关的)。如果您仍然对此感兴趣,我建议您发布另一个问题。
亚当·罗宾逊

1
@ b1nary.atr0phy为什么带有字段?例如,如果我声明了一个method int One(),则实现public int One(){return 1;}不是一个字段。
Hi-Angel

131

C#中的接口旨在定义类将遵守的协定-而不是特定的实现。

本着这种精神,C#接口确实允许定义属性-调用者必须为以下属性提供实现:

interface ICar
{
    int Year { get; set; }
}

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

class Automobile : ICar
{
    public int Year { get; set; } // automatically implemented
}

5
公开的内容不是合同的一部分吗?如果某个班级具有公开的int Year,难道不是说班级合同上要显示并可以访问Year类型的字段吗?
Didier A.

1
迟到了聚会,但是没有,在这种情况下,这意味着合同具有一个“财产年”,任何守旧阶级都应该执行该财产年。属性实际上是获取/设置方法,如果不需要特殊逻辑,则会自动生成后备字段。特殊语法只是一种更清晰的表示法。
user3613916 2014年

如何为自动实现定义一个恒定的默认值(如123)Year
lama12345 '17

1
@ lama12345我也来晚了,但是自C#6(2015,.NET Framework 4.6和.NET Core)以来,您可以使用自动属性来实现此目的。public int Year => 123;。但是,在这种情况下,没有设置器是没有意义的,因此必须使用int Year { get; }
EriF89

56

声明为属性:

interface ICar {
   int Year { get; set; }
}

47
问题是“ 为什么 C#接口不能包含字段?”。这没有解决。
AakashM 2010年

14
我同意这个答案不会回答OP问题,但是很好,它解决了我的问题。
Gorgen

36

埃里克·利珀特(Eric Lippert)钉上了钉子,我将用另一种方式来表达他的意见。接口的所有成员都是虚拟的,它们都需要被继承该接口的类覆盖。您没有在接口声明中显式地编写virtual关键字,也没有在类中使用override关键字,这是隐含的。

virtual关键字是在.NET中使用方法和所谓的v表(方法指针数组)实现的。overlay关键字使用不同的方法指针填充v-table插槽,从而覆盖了基类产生的指针。属性,事件和索引器在后台被实现为方法。但是领域却不是。因此,接口不能包含字段。


您为Eric提到的广告位概念提供了技术/实际名称(v表)。谢谢您详细了解汉斯。
RBT

如果不允许接口的默认实现,那么v表的意义是什么?这在C#8.0中发生了变化,但这不重要。
AnthonyMonterrosa

19

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

接口不包含字段,因为字段代表数据表示的特定实现,暴露它们会破坏封装。因此,具有与字段的接口将有效地编码为实现,而不是接口,这是接口具有的奇怪悖论!

例如,您的Year规范的一部分可能要求ICar实现者不允许将Year其分配给当前年份+ 1或1900之前的版本。对于您来说,如果您公开了Year字段,则无法说清楚-更好地使用属性来代替这里的工作。


18

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

使用您的建议考虑这种情况:

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 { }

这里有两个问题:

  • 因为接口的所有成员(按照定义)都是公共的,所以我们的支持变量现在向使用该接口的任何人公开
  • 哪个myBackingVariableMyClass用?

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

public interface IMyInterface
{
    int MyProperty { get; set; }
}

public abstract class MyInterfaceBase : IMyInterface
{
    int myProperty;

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

7

其他人给出了“为什么”的信息,所以我只补充说您的界面可以定义一个控件;如果将其包装在属性中:

public interface IView {
    Control Year { get; }
}


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

3

接口不包含任何实现。

  1. 用属性定义一个接口。
  2. 此外,您可以在任何类中实现该接口,并继续使用该类。
  3. 如果需要,您可以在类中将此属性定义为virtual,以便您可以修改其行为。

3

已经说了很多,但是为了简单起见,这是我的观点。接口旨在具有要由使用者或类实现的方法协定,而不要具有用于存储值的字段。

您可能会争辩说,为什么要允许使用属性?因此,简单的答案是-属性在内部仅定义为方法。


1
如果您需要访问成员,只需将其设置为属性即可。
Unome

0

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


0

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

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

奇怪的是,实际上可以将接口定义为内部接口,但是它限制了该接口的实用性,并且通常用于定义其他外部代码不使用的内部功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.