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

时间:2010-01-22 05:02:30

标签: c# interface

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

12 个答案:

答案 0 :(得分:226)

虽然许多其他答案在语义层面上是正确的,但我发现从实现细节层面处理这些问题也很有意思。

接口可以被视为 slots 的集合,其中包含 methods 。当类实现接口时,该类需要告诉运行时如何填充所有必需的槽。当你说

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方法。但字段不是方法。没有与某个字段相关联的“插槽”,您可以随后通过对字段位置的引用来“填充”该字段。因此,接口可以定义方法,属性,索引器和事件,但不能定义字段。

答案 1 :(得分:126)

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

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

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

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

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

答案 2 :(得分:48)

将其声明为属性:

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

答案 3 :(得分:35)

Eric Lippert钉了它,我会用不同的方式说出他说的话。接口的所有成员都是虚拟的,它们都需要被继承接口的类覆盖。您没有在接口声明中显式编写virtual关键字,也没有在类中使用override关键字,这些都是隐含的。

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

答案 4 :(得分:19)

为什么不只有一个Year属性,这完全没问题?

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

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

答案 5 :(得分:17)

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

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

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

我们在这里有几个问题:

  • 因为接口的所有成员 - 根据定义 - 是public,所以我们的支持变量现在暴露给使用该接口的任何人
  • myBackingVariableMyClass使用哪个?

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

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

public abstract class MyInterfaceBase : IMyInterface
{
    int myProperty;

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

答案 6 :(得分:5)

其他人已经给出了'为什么'所以我只是添加你的界面可以定义一个控件;如果你将它包装在一个属性中:

public interface IView {
    Control Year { get; }
}


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

答案 7 :(得分:3)

接口不包含任何实现。

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

答案 8 :(得分:2)

已经说了很多,但为了简单起见,这是我的看法。 接口旨在使消费者或类实现方法契约,而不是具有存储值的字段。

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

答案 9 :(得分:0)

为此你可以有一个Car基类来实现year字段,所有其他实现都可以从它继承。

答案 10 :(得分:0)

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

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

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

答案 11 :(得分:0)

从C#8.0开始,接口可以为成员(包括属性)定义默认实现。为接口中的属性定义默认实现的情况很少见,因为接口可能未定义实例数据字段。

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/interface-properties

interface IEmployee
{
    string Name
    {
        get;
        set;
    }

    int Counter
    {
        get;
    }
}

public class Employee : IEmployee
{
    public static int numberOfEmployees;

    private string _name;
    public string Name  // read-write instance property
    {
        get => _name;
        set => _name = value;
    }

    private int _counter;
    public int Counter  // read-only instance property
    {
        get => _counter;
    }

    // constructor
    public Employee() => _counter = ++numberOfEmployees;
}