为什么C#不允许调用base.SomeAbstractMethod
这是一些用于讨论的代码
abstract class ClassA
{
public abstract void StartProcess();
}
class ClassB : ClassA
{
public override void StartProcess()
{
Console.WriteLine("ClassB: Render");
}
}
class ClassC : ClassA
{
public override void StartProcess()
{
base.StartProcess();//This is where the compiler complains
Console.WriteLine("ClassC: Render");
}
}
在每个人都跳下我的喉咙之前,让我说我完全知道为什么它不会。但在某些情况下,能够这样做是有意义的,并且可以避免必须将基类的方法声明为虚拟方法但具有空实现。
来自 Delphi 的背景,我们可以在 Delphi 中做到这一点,并在我们的类设计中使用它。如果您错误地调用了基类上的抽象方法(在运行时),则会出现“抽象错误”。
然后我希望(Delphi)编译器之前检查我! 现在我希望 (C#) 编译器允许我这样做! 这有多奇怪?
问题: 难道编译器/Jitter 不能简单地忽略这样的调用并发出警告而不是错误吗? 其他人看到/感受到这种痛苦吗?
我的情况,我需要这个的情况如下: ClassA 是库的一部分(无法控制此类) 生成 ClassC(有点像编译 ASP.NET 页面或编译 Razor View 的方式。
但是该库的用户可以定义 ClassB,然后 ClassC 将从 ClassB 而不是 ClassA 派生(生成时)。类似ASP.NET 页面通常如何从 System.Web.UI.Page 继承,但如果您定义了自己的“基”页面,并且应用程序中的其他页面现在从您的基页面继承,则生成的类从您的基页面继承(这是从 System.Web.UI.Page 继承而来的,
我希望这部分是清楚的。然后查看我提供的代码,我无法获取 ClassC 的实例来调用 ClassB 的实现,因为代码生成。不知道要包含base.StartProcess()
。 看来有些人没看懂我写的东西。假设您正在编写生成从 ClassA 派生的 ClassC 的代码生成部分。好吧,由于该方法是抽象的(在 ClassA 中),因此您无法生成调用 StartProcess() 的代码行(因为编译器不允许这样做)。因此,如果有人定义了 ClassB,代码生成仍然不会调用 base.StartProcess()。这实际上就是 ASP.NET MVC 视图中发生的情况。
理想情况下,我希望编译器忽略它。它忽略了许多事情,例如对空引用调用 dispose。
我试图进行讨论而不是向...
EDIT2 假设我们有一个如上面代码所示的层次结构并且它有效。 我们现在拥有的机会是,基类 ClassA 可以(将来)为 StartProcess() 后代调用它。今天实现这一点的唯一方法是定义没有主体的虚拟方法。但这对我来说感觉有点恶心。
Here is some code for the discussion
abstract class ClassA
{
public abstract void StartProcess();
}
class ClassB : ClassA
{
public override void StartProcess()
{
Console.WriteLine("ClassB: Render");
}
}
class ClassC : ClassA
{
public override void StartProcess()
{
base.StartProcess();//This is where the compiler complains
Console.WriteLine("ClassC: Render");
}
}
Before everyone jumps down my throat, let me just say that I'm fully aware of the why it does not. But there are cases where being able to do so would make sense and prevent having to declare the base class's method as virtual but with an empty implementation.
Coming from Delphi background, we could do this in Delphi and used it in our class design. If you made the mistake of calling the abstract method on the base class (at run time) you've got an "Abstract Error".
Then I wished the (Delphi) complier check me before!
Now I wish the (C#) complier would let me do this!
How weird is that?
The Questions:
Could not the complier/Jitter simply ignore such a call and issue a warning instead of error?
Do others see/feel this pain?
The case I have, where I need this is the following:
ClassA is part of a library (no control over this class)
ClassC is generated (kind of like how an ASP.NET page gets compiled or a Razor View gets compiled.
But a user of the library can define a ClassB and then ClassC will descend from ClassB instead of ClassA (when it gets generated). Similar to how ASP.NET pages normally descend from System.Web.UI.Page but if you've defined your own "base" page and other pages in your app now descendant from your base page then the generated class descends from your base page (which is turn descends from System.Web.UI.Page).
I hope that part is clear. Then looking at the code I've presented, I can't get instances of ClassC to call into the implementation of ClassB because the code gen doesn't know to include base.StartProcess().
EDIT
It seem that some people didn't quite get what I've written. So let's say you were writing the code generation part that generates ClassC that descends from ClassA. Well, since the method is anstract (in ClassA) you can't generate the line of code that calls into StartProcess() (because the complier won't allow it). As a result, if anyone define a ClassB, the code generation still won't call base.StartProcess(). This is in fact what happens in ASP.NET MVC views.
Ideally I'd like the compiler to ignore it. It ignore many things like calling dispose on a null reference for example.
I'm trying to have a discussion rather be preached to...
EDIT2
Let's assume we have a hierarchy as shown in the code above and it worked.
The opportunity we have now is that the base class, ClassA could have an implementation (in the future) for StartProcess() descendants would call into it. The only way to do this today is to define the method virtual with no body. But that feels a bit icky to me.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
当
base.StartProcess()
被声明为抽象时,调用它怎么可能有意义呢?不可能有可调用的实现,因此编译器禁止它。就我个人而言,我喜欢在编译时看到错误,而不是在执行时看到错误或使 JITter 忽略我专门进行的调用。如果它返回一个分配给变量的值怎么办?如果该方法不存在,该变量值应该是什么?
如果 ClassC 要从 ClassB 派生,那么您不会遇到问题 - 因为您不会调用抽象基方法。但是您的代码声明它直接派生自 ClassA,而不是 ClassB。如果生成了ClassC,则应该将其生成为派生自ClassB,这样就可以了。
我个人认为编译器在这里做了完全正确的事情。
编辑:只是为了让我认为适当的解决方案绝对清楚:
base.M()
,您应该使其成为具有无操作实现的虚拟方法,而不是抽象方法。M
实现的类的情况下才应生成对base.M()
的调用,那么正确的做法取决于代码生成器 - 语言不应该仅仅因为编写了一个工具就让其他人受苦(通过将错误报告推迟到执行时间,或者更糟糕的是通过简单地执行无操作来吞掉该错误)错误地。我认为使调用抽象基方法成为执行时错误或使其成为无操作的缺点比问题中描述的问题更糟糕。
现在,一个有趣的语言功能在这里可能有用,那就是虚拟方法的想法,它强制覆盖在覆盖之前或之后调用基本实现......以类似于如何派生类中的构造函数始终必须直接或通过另一个构造函数调用基类中的构造函数。我强烈怀疑这样的功能的复杂性(返回值会发生什么?如何使用指定之前/之后语义?异常怎么样?)会超过其好处。在简单的类层次结构中,模板方法模式可以以更简单的方式执行相同的任务。
How could it possibly make sense to call
base.StartProcess()
when that's been declared to be abstract? There can't possibly be an implementation to call, so the compiler prohibits it.Personally I like seeing errors at compile-time instead of either seeing an error at execution time or making the JITter ignore a call which I've specifically made. What if it returned a value which you assigned to a variable? What should that variable value be if the method doesn't exist?
If ClassC is going to derive from ClassB, then you won't get the problem - because you won't be calling into an abstract base method. But your code declares that it derives directly from ClassA instead, not ClassB. If ClassC is generated, it should be generated to derive from ClassB instead, which would be fine.
Personally I think the compiler is doing exactly the right thing here.
EDIT: Just to make it absolutely clear what I believe the appropriate solutions are:
base.M()
from any derived class, you should make it a virtual method with a no-op implementation, instead of an abstract method.base.M()
only in the situations where it's generating a class whose base class has an implementation ofM
, then it's up to the code generator to get that right - the language shouldn't make everyone else suffer (by deferring error reporting to execution time, or worse still swallowing that error by simply performing a no-op) just because one tool has been written incorrectly.I think the downsides of either making it an execution-time error to call an abstract base method or making it a no-op are worse than the issues described in the question.
Now an interesting language feature which could potentially be useful here would be the idea of a virtual method which forced overrides to call the base implementation either before or after the override... in a similar way to how a constructor in a derived class always has to call a constructor in the base class either directly or via another constructor. I strongly suspect that the complexity of such a feature (what would happen to the return value? How would use specify before/after semantics? What about exceptions?) would outweigh the benefits. In simple class hierarchies, the template method pattern can perform the same duty in a simpler way.
我认为让编译器编译这样的代码是没有意义的。
另一方面我也理解你现在的处境。应该在代码生成器上进行修复:它不应生成对抽象方法的调用(可以使用反射进行检查)。如果您无权访问代码生成器的代码,恐怕您没有很多选择...
您可以创建一个派生自 A 的外观对象,但将所有抽象方法实现为空虚拟方法并操作代码生成器使用它来代替 A。
I don't think it would make sense to let the compiler compile such code.
On the other side I understand the situation in which you are. The fix should be made on the code generator: it should not generate calls to abstract methods (can be checked using reflection). If you do not have access to the code of the code generator I am afraid you do not have many options...
You could create a facade object that is derived from A but implements all abstract methods as empty virtual ones and manipulate the code generator to use it instead of A.
我明白你的意思了。有时,不关心基类方法是否抽象可能会很方便。但是,子类已经与其父类非常耦合,以至于编译器确切地知道什么调用有效并相应地发出错误消息。没有虚拟基类。
您可以做的是定义一个适配器类。一种无操作,只会实现抽象方法而不执行任何操作。如果它们返回值并且您无法决定返回什么默认值,则可能不可行。您现在可以从适配器派生并调用其非抽象方法。
更新
您可以通过使用反射来解决您的“需求”。而不是这样:
您可以使用类似这样的内容:
如果基类不是抽象的,则仅才会在基类上调用
StartProcess
。这是让它工作的丑陋代码(还考虑了参数和默认返回值):
值得吗?我会让你来决定。
I see what you mean. Sometimes it might be convenient to just don't care whether a base class method is abstract or not. But, a subclass is already very coupled to its parent class, so much that the compiler knows exactly what calls are valid and issues error messages accordingly. There're no virtual base classes.
What you can do is define an adapter class. Kind of a no-op that will just implement the abstract methods to do nothing. It might not be feasible if they return values and you can't decide what default value to return. You would now derive from the adapter and call its non-abstract methods.
UPDATE
You can solve your "requirement" by using reflection. Instead of this:
You'd use something like this:
This would call
StartProcess
on your base class only if it's not abstract.Here's the ugly code to make it work (which also considers parameters and default return values):
Is it worth it? I'll let you decide.
您正在从
ClassA
派生ClassC
,您希望 base.StartProcess 实际执行什么操作?你真的想从
ClassB
派生吗You are deriving
ClassC
fromClassA
, what would you expect base.StartProcess to actually do?Do you really mean to derive from
ClassB