我想知道在非抽象类中限制抽象方法的设计背后的原因(在C#中)。
我理解类实例不具有定义,因此它们不可调用,但是当定义静态方法时,它们也被排除在实例之外。为什么抽象方法不以这种方式处理,任何具体原因相同?
可以在具体类中允许它们,并且可以强制派生类实现方法,基本上就是抽象类中抽象方法的情况。
答案 0 :(得分:42)
首先,我认为你提出的问题在逻辑上并不合理。如果你有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
派生自{ {1}}和Animal
是MakeSound()
方法。这段代码应该怎么做?你没有说清楚,但我可以想象很少的选择:
您不能在键入为abstract
的变量上调用MakeSound()
,您只能使用键入的变量作为派生类之一调用它,因此这是编译错误。
这不是一个好的解决方案,因为Animal
的重点是能够将派生类的实例视为基类,并且仍然可以获得特定于派生类的行为。如果你想要这个,只需将一个普通的(abstract
,abstract
或virtual
)方法放入每个派生类中,不要对基类做任何事情。
您无法在运行时类型实际为override
的对象上调用MakeSound()
,因此这是运行时错误(例外)。
这也不是一个好的解决方案。 C#是一种静态类型的语言,所以它试图在编译时捕获诸如“你不能调用这个方法”之类的错误(有反射和Animal
之类的明显例外),所以把它变成运行时错误不会适合其他语言。此外,您可以通过在基类中创建引发异常的dynamic
方法来轻松完成此操作。
总而言之,你想要一些没有多大意义的东西,糟糕的设计气味(基类与其派生类的行为不同),并且可以很容易地解决。这些都是应该不实现的功能的所有内容。
答案 1 :(得分:11)
所以,你想要允许
class C { abstract void M(); }
编译。假设它确实如此。当有人
时你会想要发生什么?new C().M();
?你想要一个执行时错误?好吧,通常C#将编译时错误更喜欢执行时错误。如果你不喜欢这种哲学,那么还有其他语言......
答案 2 :(得分:4)
我认为你已经回答了自己的问题,最初没有定义抽象方法。因此,班级无法实现。你说它应该忽略它,但是根据定义,当添加一个抽象方法时,你会说“由此创建的每个类都必须实现这个{abstract method}”,因此你定义抽象类的类也必须是抽象的,因为那时抽象方法仍未定义。
答案 3 :(得分:1)
您可以使用“虚拟”方法实现所需的功能,但使用虚拟方法可能会导致更多的运行时业务逻辑错误,因为开发不会“强制”实现子类中的逻辑。
我认为这里有一个有效的观点。抽象方法是完美的解决方案,因为它会“强制”在儿童中定义方法体的要求。
我遇到很多情况,父类必须(或者更有效率)实现某些逻辑,但“只有”孩子可以实现其余的逻辑“
因此,如果有机会,我会很乐意将抽象方法与完整方法相结合。
@AakashM,我感谢C#更喜欢编译时错误。我也一样。任何人也是如此。这是关于开箱即用的想法。
支持这一点不会影响到这一点。
让我们在这里开箱即用,而不是对大男孩的决定说“欢呼”。
C#编译器可以检测并拒绝某人直接使用抽象类,因为它使用“abstract”关键字。
C#也知道强制任何子类实现任何抽象方法。怎么样?因为使用了“abstract”关键字。
对于研究过编程语言内部的人来说,这很容易理解。
那么,为什么C#不能检测普通类中方法旁边的“abstract”关键字并在COMPILE TIME处理它。
原因是它需要“重新加工”,并且努力不值得支持小额需求。
特别是在这个行业中,缺乏那些大男孩给他们提供的盒子的人。
答案 4 :(得分:1)
抽象类可能包含抽象成员。如果任何方法具有我们不能在同一类中实现的抽象关键字,则只有方法声明。因此抽象类是不完整的。这就是为什么没有为抽象类创建对象的原因。
非抽象类不能包含抽象成员。
示例:
b
答案 5 :(得分:0)
目前仍不清楚为什么要这样,但另一种方法可能是强制派生类提供委托实例。像这样的东西
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)
当然只是一个例子。)
否则,我可以推荐其他答案来解释为什么你的愿望可能不会让世界变得更好。
答案 6 :(得分:0)
所以上面的答案是正确的:有抽象的方法使这个类具有内在的抽象性。如果你不能实例化一个类的实例,那么就不能实现类本身。但是,上面的答案并没有真正讨论你的选择。
首先,这主要是 public 静态方法的问题。如果这些方法不是公开的,那么您可以使用受保护的非抽象方法,这些方法在抽象类声明中是允许的。因此,您可以将这些静态方法移动到单独的静态类中,而不会出现太多问题。
作为替代方案,您可以将这些方法保留在类中,但是不要使用抽象方法,而是声明接口。本质上,您有一个多继承问题,因为您希望派生类从两个概念上不同的对象继承:具有公共静态成员的非抽象父级和具有抽象方法的抽象父级。与其他一些框架不同,C#允许多重继承。相反,C#提供了一个正式的接口声明,旨在填补此目的。而且,抽象方法的全部意义实际上只是强加一定的概念界面。
答案 7 :(得分:-1)
我的场景非常类似于OP试图实现的场景。在我的例子中,我想要使用抽象的方法是 protected 方法,并且只能为基类所知。所以&#34;新的C()。M();&#34;不适用,因为有问题的方法不公开。我希望能够在基类上实例化和调用公共方法(因此它需要是非抽象的),但是我需要这些公共方法来调用子类中受保护方法的受保护实现,并且没有默认实现在父母。在某种程度上,我需要强制后代覆盖该方法。由于依赖注入,我不知道编译时子类是什么。
我的解决方案是遵循规则并使用具体的基类和虚拟保护的方法。但是,对于默认实现,我抛出NotImplementedException并带有错误&#34;必须在子类的实现中提供方法名称的实现。&#34;
protected virtual void MyProtectedMethod()
{
throw new NotImplementedException("The implementation for MyProtectedMethod must be provided in the implementation of the child class.");
}
通过这种方式,永远不会使用默认实现,后代实现的实现者很快就会发现他们错过了重要的一步。