【问题标题】:Why can't C# interfaces contain fields?为什么 C# 接口不能包含字段?
【发布时间】:2011-01-08 01:51:29
【问题描述】:

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

【问题讨论】:

  • 接口没有实现,为此使用抽象类,属性为 Year
  • 补充一下这里所说的,接口是契约,而字段是实现细节,因为它定义了机器内存中的一个槽,用于放入一个值(标量或地址指针)。
  • 但是如果该字段是公开的,它就是合同的一部分,而不仅仅是一个实现细节,对吧?

标签: c# interface


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 如果您需要访问成员,只需将其设为属性即可。
    【解决方案3】:

    接口不包含任何实现。

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

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

        【讨论】:

        • 您为 Eric 提到的插槽概念提供了技术/实际名称 (v-table)。谢谢汉斯的详细介绍。
        • 如果接口不允许默认实现,那么 v-table 的意义何在?这在 C# 8.0 中发生了变化,但这不是重点。
        【解决方案6】:

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

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

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

        【讨论】:

        • 我有时会错过的一件事是定义接口级常量的类似 java 的能力,这可能不需要“插槽”来支持该语言。
        • 我喜欢简单的解释。谢谢。 “CLR via C#”和“Essential .net volume 1”提供了更多详细信息。
        • 为什么字段没有槽?和运营商同样的问题?我记得听说过使用反射来查看是否实现了接口的鸭子类型,即使该类没有继承该接口。为什么不能使用反射(或槽)来拉起一个场?我仍在编写我的代码,所以我可能不需要/想要字段,但我惊讶地发现我不能使用运算符。运算符与我理解的方法完全相同,但并非所有都可以重载(An interface cannot contain constants, fields, operators。来自msdn.microsoft.com/en-us/library/ms173156.aspx
        • @acidzombie:接口为什么不能定义运算符的问题与为什么它不能包含字段不同(尽管可能相关);如果您仍然对此感兴趣,我建议您发布另一个问题。
        • @b1nary.atr0phy 为什么使用字段?例如。如果我声明了一个方法int One(),那么实现public int One(){return 1;} 就不是一个字段。
        【解决方案7】:

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

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

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

        【讨论】:

          【解决方案8】:

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

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

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

          这里有几个问题:

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

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

          public interface IMyInterface
          {
              int MyProperty { get; set; }
          }
          
          public abstract class MyInterfaceBase : IMyInterface
          {
              int myProperty;
          
              public int MyProperty
              {
                  get { return myProperty; }
                  set { myProperty = value; }
              }
          }
          

          【讨论】:

            【解决方案9】:

            C# 中的接口旨在定义一个类将遵守的契约——而不是特定的实现。

            本着这种精神,C# 接口确实允许属性被定义——调用者必须为这些属性提供一个实现:

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

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

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

            【讨论】:

            • 不是所有公开的东西都是合同的一部分。如果一个类有 public int Year,它不是说类契约有一个 Year 类型的字段存在于它上面,并且可以访问吗?
            • 迟到了,但是没有,在这种情况下,这意味着合同有一个财产年,任何遵守阶级都应该实施。属性实际上是 get/set 方法,如果不需要特殊逻辑,它们会自动生成支持字段。特殊语法只是为了更清晰的符号。
            • 如何为Year的自动实现定义一个常量默认值(如123)?
            • @lama12345 我也迟到了,但从 C# 6(2015,.NET Framework 4.6 和 .NET Core)开始,您可以为此目的使用自动属性。 public int Year => 123;。但是,在这种情况下,使用 setter 是没有意义的,因此必须使用 int Year { get; } 定义接口
            • LBushkin 您的帖子必须已被标记为最佳答案,
            【解决方案10】:

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

            【讨论】:

              【解决方案11】:

              为什么不只拥有一个 Year 属性,这非常好?

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

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

              【讨论】:

                【解决方案12】:

                将其声明为属性:

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

                【讨论】:

                • 问题是“为什么 C#接口不能包含字段?”。这并没有解决这个问题。
                • 我同意这个答案不回答 OP 问题,但是,它解决了我的问题。
                猜你喜欢
                • 2011-01-03
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-05-10
                • 2016-03-01
                相关资源
                最近更新 更多