关于c#:为什么抽象类需要从它们实现的接口定义抽象方法?

Why do abstract classes need to define abstract methods from interfaces they implement?

当抽象类实现接口时,还需要定义或声明方法(如前所述):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface MyInterface
{
  void Method();
}

public abstract class MyAbstractClass : MyInterface
{
  public abstract void Method(); // required, even though MyAbstractClass does not implement
}

public class MyClass : MyAbstractClass
{
  public override void Method()
  {
  }
}

public interface MyInterface2 : MyInterface
{
  // no need to declare Method() -- why can't abstract classes do the same for unimplemented methods?
}

C语言需要定义实现接口的抽象类的抽象方法的设计原理是什么?抽象类需要定义一个它没有实现的方法,这似乎是完全多余的(更糟的是,对于实际实现该方法的类,必须将该方法标记为override)。我看不出为什么抽象类不能像MyInterface2那样工作,它继承自MyInterface,但不需要声明MyInterface的方法。

  • 接口就是这样工作的。它们保证方法存在,即使抽象类将其声明为抽象的;这意味着继承该类的任何人都必须实现该方法,这意味着支持接口。编辑:这是我对它工作原理的微小理解。
  • 否则,您将得到一个奇怪的层次结构,其中从抽象类派生的任何内容都需要记住,它是从实现所述接口的类派生的。
  • 如果不希望抽象类具有接口方法,可以将接口添加到具体类,而不是抽象类。
  • 感觉就像一个马虎的心理模型
  • 在这种情况下,它是多余的,因为抽象类不做任何事情。
  • 我看不出抽象类不能像从其他接口继承的接口那样工作的原因;继承者接口不需要声明继承者的方法和属性。
  • @普雷斯顿:那什么时候不多余呢?
  • 我不明白为什么这个问题被标记为基于意见。C语言设计人员对语言的设计似乎相当理性,这个问题是询问特定需求的基本原理是什么。重述问题以强调这一点。


抽象类是完全成熟的类型,只是它不能实例化。因此,即使其某些方法没有实现,也必须声明其完整合同。特定抽象类的用户必须能够绑定到其所有方法,无论这些方法来自接口还是直接在抽象类中声明。接口中的方法没有(至少)在抽象类中声明(如果甚至没有实现),使用的方法无法绑定到它们。

此外,类和接口是松散耦合的。类可以声明一个方法,该方法随后映射到由其子代实现的接口中的同一签名方法。因此,从语言设计的角度来看,要求直接在抽象类上实现接口的所有方法,以便在抽象类中实际声明,这也是一个"好主意"。

您可以将接口视为可分离的特性(除非使用显式实现)。抽象类可以独立存在,它的直接用户不需要知道它的任何接口。

由于抽象类的整个概念在实践中需要充分利用虚拟方法的技术机制,因此只有虚拟方法才能在没有实现的情况下保留,这是一种设计特征。

更新:示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface IFoo { void M(); }

public abstract class Bar : IFoo
{
    public virtual abstract void M();

    public void N() { }
}

public class Baz : Bar
{
    public override void M() {}
}



public void Method(Bar par)
{
    par.M();
}



Baz x = new Baz();
Method(x);

Method见变量x表示为Bar—既不表示Baz,也不表示IFoo。换句话说,类Bar的用户不关心它是否实现了一个、两个、十个或根本没有接口。它所做的就是访问它的成员。它依赖于Bar的合同,而不是IFoo的合同。因此,如果Bar实施IFoo,则必须定义IFoo中的所有成员。

  • 我认为答案是正确的,尽管我不明白为什么你说"抽象类是完全成熟的类型",如果"它不能被实例化"。继承者接口的用户可以很好地绑定到其方法,即使继承者接口没有定义继承者的方法。
  • "完全成熟的类型"我的意思是它在声明方面是完整的。它可以在任何允许类型的地方使用。唯一的例外是它不能在运行时实例化,因为它在实现方面是不完整的。抽象类的原因只是:允许不完整的实现,但需要完整的声明。
  • 那么抽象类在哪里可以使用而不是接口呢?
  • 课程是一个更丰富的概念。不管它们是否抽象。你最后一个问题没有"微不足道"的答案。
  • 也许,谢谢你花时间回答,当然,这对我来说还是不满意的。我希望约翰·斯基特能跳进去!
  • 此外:当一个抽象类从另一个抽象类继承时,继承者不需要定义继承者的抽象方法。因此,我再一次不明白为什么当抽象类实现接口时,应该强制它定义不实现的方法。也许有一个很好的理由,但我看不出来。
  • 对不起,我看不出你的例子如何帮助证明你的观点。如果方法的参数是baz类型,那么方法可以调用par.n(),即使在baz中没有声明n()(它是继承的条)。对我来说,这与不定义从接口继承的抽象方法相同。
  • 好吧,这不是同一个-那是继承:-)我认为你最好读一些关于OOP原则的书。到目前为止,你似乎倾向于成为一个"不信者",而不是考虑争论。
  • 我尽量不受非理性怀疑主义的影响,尽管我确信我对此并不免疫。我想我确实在混合继承和实现,但在本例中,我认为这不是错误的。抽象类确实模糊了继承和实现之间的区别(因此接口和抽象类之间的细微区别,如stackoverflow.com/a/618877/68936所示(例如)。
  • "创建派生类和实现接口之间最重要的技术区别是派生类只能继承一个基类,但类可以实现任意数量的接口。"(msdn.microsoft.com/en-us/library/ms973861.aspx)。对我来说,这似乎是一个很细微的区别。这让我认为(假设没有其他问题),实现接口的抽象类从它们"实现/继承自"的接口"继承"它们不实现的方法是合理的。


在实现接口的抽象类中创建的方法不需要是抽象的。抽象类也可以包含非抽象方法,您可以在那里定义接口的完整实现。继承类不需要重写这些方法中的任何一个。

此外,在msdn中对抽象类进行了描述:

An abstract class must provide implementation for all interface members.

An abstract class that implements an interface might map the interface methods onto abstract methods.

注意"可能"这个词,它不是"必须"。

请注意,msdn对实现接口的说明:

When a class or struct implements an interface, the class or struct must provide an implementation for all of the members that the interface defines.

对于各种类和结构都是如此。

  • 很好地参考了msdn;文档始终是一个很好的起点。
  • 我认为OP关注的是为什么抽象类中必须有接口方法的任何声明,不管它本身是抽象的还是特定的。你的回答似乎在讨论别的问题。
  • @Ondrejucny我只是在这个话题上更具体地加了一点。另外,我对你的答案投了反对票,因为它也提供了很好的解释。
  • 我认为在编辑之后,你的答案在技术上是完整的。另一方面,我的答案提供了相当多的元解释,而不需要从某种格式的角度来看完整性。我们应该把他们联合起来。
  • @正确-我关心的是为什么抽象类中必须有接口方法的任何声明。
  • @首先,您只需要实现语言所要求的所有接口方法。其次,您可能在抽象类中具有代码的方法,并且不需要在继承类中重写它们。
  • @我的问题是"为什么语言需要声明抽象方法"。声明语言需要它不是一个很好的答案,第二,我不是在问"代码方法"。


用方法void Bar()实现IFoo的类可以选择是否公开一个公共方法Bar(),或者是否希望使用一个非公共成员来实现接口〔C〕要求直接实现方法是公共的还是私有的;vb.net还允许protected的作用域,其中对于抽象类来说,h通常是最有用的类型。

抽象类可以通过两种有用的方法实现IFoo.Bar()

1
2
3
4
5
6
public abtract void Bar();

// or

protected abstract void IFoo_Bar(); // Note that the exact name is arbitrary
void IFoo.Bar() { IFoo_Bar(); }

这两种实现都是合理的。第一种假设不合理,认为类希望有一个在其代码中不存在的公共方法;第二种假设要求编译器"猜测"应该为抽象方法指定什么名称。因为没有比任何选择更好的行为,C要求程序员指定所需的行为。