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

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

194462 次浏览

将其声明为属性:

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

为什么不直接有Year属性,这是完全没问题的?

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

例如,部分Year规范可能要求ICar实现者允许赋值给当前年份+ 1之后或1900年之前的Year是无效的。没有办法说如果你已经暴露了Year字段——使用属性来代替这里的工作要好得多。

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

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

在这种精神下,c#接口定义允许属性-调用者必须为其提供一个实现:

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

实现类可以使用auto-properties来简化实现,如果没有与属性相关的特殊逻辑的话:

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

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

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

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 ?

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

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. 如果需要,可以在类中将此属性定义为virtual,以便修改其行为。

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

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

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

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

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

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

这个类说:“当你创建一个我的实例时,填充一个对Foo的引用。M在IFoo.M的槽里。

然后当你打电话的时候:

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

编译器会生成这样的代码:“询问对象IFoo的槽中有哪个方法。M,然后调用这个方法。

如果接口是包含方法的槽的集合,那么其中一些槽还可以包含属性的get和set方法、索引器的get和set方法以及事件的添加和删除方法。但字段不是方法。没有与字段相关联的“槽”,然后您可以用字段位置的引用“填充”该字段。因此,接口可以定义方法、属性、索引器和事件,但不能定义字段。

Eric Lippert说的很好,我会用另一种方式来表达他说的话。接口的所有成员都是虚的,它们都需要被继承该接口的类重写。你不需要在接口声明中显式地编写virtual关键字,也不需要在类中使用override关键字,它们是隐含的。

在. net中,virtual关键字是通过方法和所谓的v-table(方法指针数组)实现的。override关键字用不同的方法指针填充v-table槽,覆盖基类生成的方法指针。属性、事件和索引器是作为方法实现的。但是田野不是。因此,接口可以不包含字段。

其他人已经给出了“为什么”,所以我只是补充说,你的界面可以定义一个控件;如果你把它包装在属性中:

public interface IView {
Control Year { get; }
}




public Form : IView {
public Control Year { get { return uxYear; } } //numeric text box or whatever
}
已经说了很多,但为了简单起见,这里是我的看法。 接口的目的是拥有由消费者或类实现的方法契约,而不是使用字段来存储值

你可能会问为什么允许有财产?因此,简单的答案是—属性在内部只定义为方法。

从c# 8.0开始,接口可以为成员定义默认实现,包括属性。很少在接口中为属性定义默认实现,因为接口可能不定义实例数据字段。

https://learn.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;
}