题 非抽象类中的抽象方法


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

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

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


35
2017-09-25 10:29


起源


我希望我不是唯一一个说......“哇?” - Lews Therin
您想要在非抽象类中定义抽象方法的用例是什么? - Vikdor
请添加一个类的示例,其中这样的概念将是有用的。 - Damien_The_Unbeliever
拿一个抽象类,比如一个抽象方法和许多具体方法。如果我没有限制,不能删除这个抽象类,仍然实现相同的目标? - Ashish Jain
如果您对此有所了解,也许您可​​以考虑制作该方法 virtual 相反,让它抛出一个 NotImplementedException (或什么也不做,或返回默认值)。然后记录继承的类 必须 实施方法。但总的来说,我同意其他人的意见;这听起来像你正在尝试使用错误的工具来完成工作。 - Chris Sinclair


答案:


首先,我认为你提出的问题在逻辑上并不合理。如果你有 abstract 方法,它基本上意味着该方法未完成(如@ChrisSinclair指出)。但这也意味着全班都没有完成,所以它也必须如此 abstract

或另一种方式:如果你有 abstract 一个没有的类的方法 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() 在变量上输入 Animal,你只能使用一个类型为派生类的变量来调用它,所以这是一个编译错误。

    这不是一个好的解决方案,因为整点 abstract 是能够将派生类的实例视为基类,并仍然获得特定于派生类的行为。如果你想要这个,那就放一个正常的(没有 abstractvirtual 要么 override)方法到每个派生类中,并且不对基类做任何事情。

  2. 你不能打电话 MakeSound() 在运行时类型实际上的对象上 Animal,所以这是运行时错误(例外)。

    这也不是一个好的解决方案。 C#是一种静态类型的语言,所以它试图在编译时捕获诸如“你不能调用这个方法”之类的错误(例如反射和 dynamic),因此将其变为运行时错误将不适合该语言的其余部分。此外,您可以通过创建一个简单的方法来完成 virtual 抛出异常的基类中的方法。

总而言之,你想要一些没有多大意义的东西,糟糕的设计气味(基类的行为与派生类不同),并且可以很容易地解决。这些都应该是一个功能的唱歌  实施。


39
2017-09-25 11:43



对我有意义,第1点给出了为什么在“类级别”中有抽象以及为什么抽象类应该限制为实例的答案。谢谢vick。 - Ashish Jain


所以,你想要允许

class C { abstract void M(); }

编译。假设它确实如此。那么当有人这么做时你会想要发生什么?

new C().M();

?你想要一个执行时错误?好吧,通常C#更喜欢编译时错误到执行时错误。如果您不喜欢这种理念,可以使用其他语言...


10
2017-09-25 10:34



我说过这个问题并给出了由编译器处理的静态方法的例子。我的问题是关于抽象方法的编译器设计,基本上为什么他们专门为这些方法选择抽象类? - Ashish Jain
@AshishJain因为一个 abstract class不能直接实例化。它是“未完成的”,就像一个 interface 无法直接实例化,因为空方法/属性的作用是未定义的。它迫使希望使用它们的程序员继承和实现 所有 在能够实例化之前的抽象方法。编辑:静态方法是一个 不同 概念。他们属于 类型 而不是对象实例。抽象的方法是在 例 级别和需要实现(还要注意静态方法需要实现 - 不存在“抽象静态”) - Chris Sinclair
如果您想要执行时错误,您可以这样做:创建一个抛出异常的虚方法。 - svick
静态方法是一种不同的情况,因为它们在任何情况下都不在实例上。我不明白你的意思是“他们为什么选择专门针对这些方法的抽象类?”。如果允许具体类具有抽象方法,我已经向您展示了潜在的问题。你宁愿发生什么? - AakashM
@AakashM如果抽象方法有一个返回类型而不是void,那就更有趣了。 :)至少一个void方法可以做到 没有,但返回类型有什么作用? default 值?是的,我不知道还会发生什么。摘要是有原因的,其他任何东西都可以处理 virtual 正如我们所说。 - Chris Sinclair


我想你已经回答了自己的问题,最初没有定义抽象方法。因此,班级无法实现。你说它应该忽略它,但是根据定义,当添加一个抽象方法时,你会说“由此创建的每个类都必须实现这个{abstract method}”,因此你定义抽象类的类也必须是抽象的,因为那时抽象方法仍未定义。


4
2017-09-25 10:36





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

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

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

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

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

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

让我们在这里开箱即用,而不是对大男孩的决定说“呐喊”。

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

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

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

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

原因是它需要“重新加工”并且努力不值得支持小额需求。

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


1
2017-10-03 08:36





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

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) 当然只是一个例子。)

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


0
2017-09-25 11:29



很好地接受替代品不是最好的,甚至有严重的缺点。但为什么当前实现的抽象语句(为什么不保持抽象方法/属性/索引器等像虚拟)是最好的,并且“抽象类”的概念是不可避免的(由于它优于其他方式),记住接口已经是那里? - Ashish Jain


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

首先,这主要是一个问题 上市 静态方法。如果这些方法不是公开的,那么您可以使用受保护的非抽象方法,这些方法在抽象类声明中是允许的。因此,您可以将这些静态方法移动到单独的静态类中,而不会出现太多问题。

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


0
2017-07-14 13:02





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

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

例:

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

}

0
2017-07-21 19:30





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

我的解决方案是遵循规则并使用具体的基类和虚拟 保护 方法。但是,对于默认实现,我抛出一个NotImplementedException,错误为“the implementation for 方法名称 必须在儿童班的实施中提供。“

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

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


-1
2017-11-08 14:14



那么你用运行时异常替换编译器错误?如果您的类不是抽象类,那么您可能会在某个时刻冒险在类的实例上调用未实现的方法并抛出异常。如果你的意思是抽象,那么只需使用抽象,不要试图解决它。 - Geert Bellekens
这不能解决问题。一旦你有足够的 声誉 你将能够 评论任何帖子;代替, 提供答案,不需要提问者澄清。 - 来自评论 - Ed DeGagne
运行时错误是一种合理的权衡,因为框架/语言没有提供其他机制来实现OP的要求。在我的情况下,我相信如果我遇到运行时错误,我会在开发过程的早期发现它。子类从未使用过或者它一直被使用(核心实用程序)。我只是提供了一个替代解决方案,需要在具体类中定义一个方法,需要在任何子类中重写。 - Jim K
编辑我不清楚我的帖子是如何通过提供框架/语言限制的解决方法直接解决OP中的问题?如果您认为我的帖子不合适,请直接与我联系。 - Jim K