【问题标题】:Interface inheritance: is extending properties possible?接口继承:扩展属性可能吗?
【发布时间】:2011-03-31 18:50:31
【问题描述】:

我想这样做:

interface IBase
{
    string Property1 { get; }
}

interface IInherited : IBase
{
    string Property1 { get; set; }
}

这样IInherited 将具有inherited 属性Property1 以及允许set 的附加功能。

这可能吗?语法是什么?

编辑:请注意我将“继承”一词用粗体表示。我是在特别询问继承财产,而不是将其隐藏在新财产后面。

【问题讨论】:

  • 我认为类的继承和接口的继承之间存在混淆。一个接口并没有真正继承另一个接口,而一个类继承了另一个接口。您的所有代码都在说 IInherited 也实现了 IBase。代码很好,它只是引发编译器警告,因为您正在更改界面中 Property1 的签名。你可以用 new 清除它。
  • 正如许多其他人所指出的,这种语法是不可能的,但我觉得它应该是。如果您在 Property1 上使用反射,则会有一个 Property1_get 方法和一个 Property1_set 方法,因此从逻辑上讲,您似乎应该能够分别实现它们。并且无法做到这一点肯定会导致我有时重复代码(或忍受 new 关键字)。
  • @cedd 我也觉得应该是这样,原因与您提到的相同。属性只是伪装的方法。 C# 中的当前设计很奇怪,会造成不必要的混乱,这就是我提出这个 SO 问题的原因。

标签: c# inheritance interface properties


【解决方案1】:

如果这样做的唯一方法是使用 new 关键字这一事实让您感到困扰,那么在我看来,您认为接口是错误的。

当然,你可以说IInherited“继承自”IBase;但这真的意味着是什么?这些是接口;他们建立代码合同。通过使用new string Property1 { get; set; } 隐藏IBase.Property1 属性,您不会隐藏任何功能。因此,许多开发人员认为隐藏是“坏”事情的传统原因——它违反了多态性——在这种情况下是无关紧要的。

问问自己:在接口方面,真正重要是什么?它们提供了响应某些方法调用的保证,对吧?

所以,给定以下两个接口:

interface IBase
{
    string Property1 { get; }
}

interface IInherited : IBase
{
    new string Property1 { set; }
}
  1. 如果一个对象实现了IBase,你可以读取它的Property1属性。
  2. 如果一个对象实现了IInherited,您可以读取它的Property1 属性(就像IBase 实现一样),也可以写入它。

同样,这里真的没有什么问题。

【讨论】:

  • 你是对的,除非是显式实现。每个版本都可以有不同的实现,因此它仍然是隐藏的。实现您的接口的人可能会意外或故意这样做,因此您应该小心如何使用这些接口。如果你想避免这个问题,并且仍然使用继承,那么使用“new”,但不要添加第二个 getter。
  • @Merlyn,同意了。通过删除第二个 get 语句,这将不再违反 LSP。
  • 您的代码为该属性声明了两个不同的 getter。在IInherited 中,您可能只想声明new string Property1 { set; }。实现IInherited 的类可以有一个带有getter 和setter 的属性,并且它将实现这两​​个接口。
  • @supercat:嗯,这值得商榷,对吧?我的意思是,就个人而言,我有点同意你的看法,担心有人明确地实现了两个独立的 get 方法,从而“破坏”你的界面设计似乎有点牵强。同时,准确定义每个getset once 在某种意义上确实看起来更“正确”。所以我不知道。无论如何,亚当的回答为任何有兴趣的人提供了另一种方法。
  • 提问者说得对,这是个问题。例如,使用您的解决方案,这将无法编译:static void M(IInherited x) { var read = x.Property1; } 原因是 IBaseProperty1another 属性(也称为 @987654341)隐藏@ 在继承链中的“稍后”点声明。但是如果是set-only,则后者的属性。在我看来,new 修饰符根本上是错误的,因为它说我们有一个不相关的属性。它不会“扩展”现有属性。正如@Timwi 所说,new 属性中带有{ get; set; } 的另一个选项也很糟糕。
【解决方案2】:

隐藏成员违反了 Liskov 替换原则,而且几乎永远不应该这样做。通过隐藏此成员,您将引入一个非常难以定位的错误,因为根据您将对象转换为 ((IInherited).Property1) 还是将其转换为 ((IBase).Property1),会出现两种不同的结果。

http://en.wikipedia.org/wiki/Liskov_substitution_principle

【讨论】:

  • -1。这不是传统意义上的隐藏,因为接口无论如何都不支持多态性。
  • Adam:我认为 Chris 是正确的,如果我们将他的评论放在显式实现接口的上下文中,具有不同的实现。
  • 实现 IInherited 的类可以被转换为具有较少修改它的权限的 IBase,它不会覆盖方法的实现,因为 getter 将返回相同的(因为两个接口共享' getter 实现)
  • @Diego, Chris:如果你明确地实现它,getter 可以有多个实现。如果您继承自(实现这些接口之一)的基类执行显式实现,您最终可能会遇到难以发现的错误。但是,克里斯,我建议您删除此内容,因为它会吸引不了解更多的人的反对票=P
【解决方案3】:

您的代码无论如何都应该可以工作......它只是因为隐藏了 Property1 而创建了一个编译器警告。使用新前缀清除 IInherited 中的此警告标记 Property1

【讨论】:

    【解决方案4】:

    不明确,不。你有两个选择:

    public interface IBase
    {
        string Property1 { get; }
    }
    
    public interface IInherited : IBase
    {
        void SetProperty1(string value);
    }
    

    或者您可以使用 new 关键字终止编译器警告:

    public interface IBase
    {
        string Property1 { get; }
    }
    
    public interface IInherited : IBase
    {
        new string Property1 { get; set; }
    }
    

    除非您显式实现IInherited.Property1,否则IBase 将自动绑定到您的可设置实现。

    【讨论】:

    • 在实现上只有一个名为 Property1 的属性。如果通过对 IInherited 的引用访问实现,则可以调用 getter 和 setter,如果通过 IBase 访问它,则只能调用 getter。但这是必需的行为不是吗?
    • 是的,这就是我想要的。但我可能不得不使用 getter/setter 方法,因为这似乎无法完成......
    • 它可以完成,就像你正在做的那样......你得到一个编译器错误吗?错误是什么?
    • 它编译得很好。但我仍然想要那里的遗产。 getter/setter 方法并不是最糟糕的想法,我只是想使用属性来提高可读性。
    • @Malki:同样,在接口方面,没有继承之类的东西。它更多的是……加法。您正在使用继承语法,但严格来说,它不是继承。只需按照您的方式进行操作并使用new
    【解决方案5】:

    不幸的是,不能这样扩展属性。但是,您可以使用 new 隐藏该属性:

    interface IInherited : IBase
    {
        // The new is actually unnecessary (you get warnings though), hiding is automatic
        new string Property1 { get; set; }
    }
    

    或者,您可以创建自己的 getter 和 setter 方法,这些方法可以被覆盖(良好的“ol Java 风格”):

    interface IBase
    {
        string GetProperty1();
    }
    interface IInherited : IBase
    {
        void SetProperty1(string str);
    }
    

    编译器实际上将属性转换为getter和setter方法。

    【讨论】:

      【解决方案6】:

      您可以使用“new”关键字标记属性,也可以跳过继承:

      public interface IBase
      {
          string Property1 { get; }
      }
      
      public interface IInherited : IBase
      {
          new string Property1 { get; set; }
      }
      

      或者:

      public interface IBase
      {
          string Property1 { get; }
      }
      
      public interface IInherited
      {
          string Property1 { get; set; }
      }
      

      不管怎样,这应该有效:

      public class SomeClass : IInherited, IBase
      {
          public string Property1
          {
              get
              {
                  // ...
              }
              set
              {
                  // ...
              }
          }
      }
      

      不过,在为接口创建继承链之前,您可能需要仔细考虑。谁会看到哪个界面?传递 IBase 时是否需要强制转换为 IInherited?如果是这样,你能保证你可以做那个演员吗(如果你允许用户创建的类,那么答案是否定的)?如果您不小心,这种继承会真正损害(重)可用性。

      【讨论】:

        【解决方案7】:

        我在工作中与我们的几位首席开发人员进行了对话,我们得出的结论是:

        • 无论哪种方式在技术上都有效。
        • 接口都是为了单一目的。

        接口用于定义任何继承类可以执行的方式。即使您的第二个接口共享部分,甚至在某些相同的条件下使用,也会定义一个接口来解释如何在与该接口相关的所有条件下使用一个类。

        此外,出于这个原因,类能够继承多个接口。在代码清晰度方面:

        public class SomeClass : IInherited, IBase
        

        上面这行明确指出 SomeClass 能够执行 IInherited 的动作和 IBase 的动作,并且其中一些动作相同并不重要。必须通过接口向后爬来发现 IInherited 扩展了 IBase 可能会让其他可能会在未来查看您的代码的开发人员感到困惑。

        在两个接口中具有相同的值可能看起来像是重复工作,但在您的代码功能中没有做出任何假设。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-10-07
          • 2014-09-07
          • 2012-09-03
          • 1970-01-01
          • 1970-01-01
          • 2019-10-05
          • 1970-01-01
          相关资源
          最近更新 更多