53

我想知道在非抽象类(在 C# 中)中限制抽象方法的设计背后的原因。

我知道类实例没有定义,因此它们不能被调用,但是当定义静态方法时,它们也被排除在实例之外。为什么不以这种方式处理抽象方法,有什么具体原因吗?

它们可以在具体类中被允许,并且可以强制派生类实现方法,基本上就是在抽象类中的抽象方法的情况下所做的。

4

8 回答 8

54

首先,我认为您的要求在逻辑上没有意义。如果您有abstract方法,则基本上意味着该方法未完成(正如@ChrisSinclair 指出的那样)。但这也意味着整个课程都没有完成,所以它也必须是abstract

或者换一种说法:如果你在一个abstractnot 的类上有一个方法abstract,那就意味着你有一个不能被调用的方法。但这意味着该方法没有用,您可以将其删除,并且它们都可以正常工作。

现在,我将尝试通过一个示例更具体:想象以下代码:

Animal[] zoo = new Animal[] { new Monkey(), new Fish(), new Animal() };

foreach (Animal animal in zoo)
    animal.MakeSound();

这里,Animal是非abstract基类(这就是为什么我可以直接把它放到数组中),Monkey并且是从方法Fish派生而来Animal的。这段代码应该做什么?你没有说清楚,但我可以想象几个选择:MakeSound()abstract

  1. 您不能调用MakeSound()类型为 as 的变量Animal,只能使用类型为派生类之一的变量调用它,因此这是一个编译错误。

    这不是一个好的解决方案,因为重点在于abstract能够将派生类的实例视为基类,并且仍然获得特定于派生类的行为。如果你想要这个,只需在每个派生类中放入一个普通(no或)方法abstract,不要对基类做任何事情。virtualoverride

  2. 您不能调用MakeSound()运行时类型实际上为 的对象Animal,因此这是运行时错误(异常)。

    这也不是一个好的解决方案。C# 是一种静态类型语言,因此它会尝试在编译时捕获诸如“您不能调用此方法”之类的错误(反射dynamic和语言。此外,您可以通过virtual在基类中创建一个引发异常的方法来轻松完成此操作。

总而言之,您想要一些没有多大意义的东西,并且带有不良设计的味道(一个行为与其派生类不同的基类)并且可以很容易地解决。这些都是不应实施的功能的迹象

于 2012-09-25T11:43:27.037 回答
14

所以,你想允许

class C { abstract void M(); }

编译。假设确实如此。当有人这样做时,您希望发生什么

new C().M();

? 您想要执行时错误吗?好吧,通常 C# 更喜欢编译时错误而不是执行时错误。如果您不喜欢这种哲学,还有其他语言可用...

于 2012-09-25T10:34:15.817 回答
4

我认为您已经回答了自己的问题,最初没有定义抽象方法。因此无法实例化该类。您是说它应该忽略它,但是根据定义,在添加抽象方法时,您是在说“由此创建的每个类都必须实现此{抽象方法}”,因此您定义抽象类的类也必须是抽象的,因为那时抽象方法仍未定义。

于 2012-09-25T10:36:07.370 回答
2

您可以使用“虚拟”方法实现您想要的,但使用虚拟方法可能会导致更多运行时业务逻辑错误,因为开发人员不会“强制”在子类中实现逻辑。

我认为这里有一个有效的观点。抽象方法是完美的解决方案,因为它将“强制”定义子方法体的要求。

我遇到过很多情况,父类必须(或者更有效地)实现一些逻辑,但“只有”孩子可以实现其余的逻辑”

因此,如果有机会,我会很乐意将抽象方法与完整方法混合使用。

@AakashM,我很欣赏 C# 更喜欢编译时错误。我也是。任何人也一样。这是关于开箱即用的思考。

支持这一点不会影响这一点。

让我们在这里跳出框框思考,而不是对大男孩的决定说“万岁”。

C# 编译器可以直接检测并拒绝某人使用抽象类,因为它使用“abstract”关键字。

C# 也知道强制任何子类实现任何抽象方法。如何?因为使用了“抽象”关键字。

对于任何研究过编程语言内部的人来说,这很容易理解。

那么,为什么 C# 不能检测到普通类中方法旁边的“抽象”关键字并在编译时处理它。

原因是它需要“返工”,付出的努力不值得支持小需求。

特别是在一个缺乏大男孩给他们的框框思考的人的行业。

于 2013-10-03T08:36:32.493 回答
2

抽象类可能包含抽象成员。如果任何方法具有我们无法在同一个类中实现的抽象关键字,则只有方法声明。所以抽象类是不完整的。这就是为什么没有为抽象类创建对象的原因。

非抽象类不能包含抽象成员。

例子:

namespace InterviewPreparation
{
   public abstract class baseclass
    {
        public abstract void method1(); //abstract method
        public abstract void method2(); //abstract method
        public void method3() { }  //Non- abstract method----->It is necessary to implement here.
    }
    class childclass : baseclass
    {
        public override void method1() { }
        public override void method2() { }
    }
    public class Program    //Non Abstract Class
    {
        public static void Main()
        {
            baseclass b = new childclass(); //create instance
            b.method1();
            b.method2();
            b.method3();
        }
    }

}
于 2018-07-21T19:30:11.857 回答
1

目前尚不清楚您为什么要这样做,但另一种方法可能是强制派生类提供委托实例。像这样的东西

class MyConcreteClass
{
  readonly Func<int, DateTime, string> methodImpl;

  // constructor requires a delegate instance
  public MyConcreteClass(Func<int, DateTime, string> methodImpl)
  {
    if (methodImpl == null)
      throw new ArgumentNullException();

    this.methodImpl = methodImpl;
  }

  ...
}

(当然,签名string MethodImpl(int, DateTime)只是一个例子。)

否则,我可以推荐其他答案来解释为什么您的愿望可能不会让世界变得更美好。

于 2012-09-25T11:29:52.783 回答
0

所以上面的答案是正确的:拥有抽象方法使类本质上是抽象的。如果你不能实例化一个类的一部分,那么你就不能实例化这个类本身。但是,上面的答案并没有真正讨论您的选择。

首先,这主要是公共静态方法的问题。如果这些方法不打算公开,那么您可以保护非抽象方法,这些方法在抽象类声明中是允许的。因此,您可以将这些静态方法移至单独的静态类,而不会出现太大问题。

作为替代方案,您可以将这些方法保留在类中,但随后不使用抽象方法,而是声明一个接口。本质上,您有一个多重继承问题,因为您希望派生类从两个概念上不同的对象继承:一个具有公共静态成员的非抽象父类,以及一个具有抽象方法的抽象父类。与其他一些框架不同,C# 确实允许多重继承。相反,C# 提供了一个正式的接口声明来满足这个目的。此外,抽象方法的全部意义实际上只是强加了某个概念接口。

于 2017-07-14T13:02:25.617 回答
0

我有一个与 OP 试图实现的非常相似的场景。在我的情况下,我想要抽象的方法将是一个受保护的方法,并且只有基类知道。所以“新的 C().M();” 不适用,因为所讨论的方法不是公开的。我希望能够在基类上实例化和调用公共方法(因此它必须是非抽象的),但是我需要这些公共方法来调用子类中受保护方法的受保护实现并且没有默认实现在父母。从某种意义上说,我需要强制后代覆盖该方法。由于依赖注入,我不知道编译时子类是什么。

我的解决方案是遵循规则并使用具体的基类和虚拟保护方法。但是,对于默认实现,我抛出 NotImplementedException 错误“必须在子类的实现中提供方法名称的实现”。

protected virtual void MyProtectedMethod() 
{ 
  throw new NotImplementedException("The implementation for MyProtectedMethod must be provided in the implementation of the child class."); 
}

通过这种方式,永远不能使用默认实现,并且后代实现的实现者很快就会发现他们错过了重要的一步。

于 2017-11-08T14:14:33.590 回答