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


当前回答

将其声明为属性:

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

其他回答

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

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

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方法以及事件的添加和删除方法。但是字段不是方法。没有与字段相关联的“槽”,然后您可以用字段位置的引用“填充”该字段。因此,接口可以定义方法、属性、索引器和事件,但不能定义字段。

将其声明为属性:

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

从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;
}

为什么不使用Year属性呢?

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

例如,部分Year规范可能要求ICar实现者允许赋值给比当前年份+ 1晚或1900年之前的年份是无效的。如果您已经公开Year字段,就无法这样说——在这里使用属性来完成工作要好得多。

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

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

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

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

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