编译器琐事:这段代码的结果是什么
我今天正在审查一些代码,遇到了一些代码(此代码片段准确地描述了)...
public abstract class FlargBase{
public FlargBase(){
this.DoSomething();
}
public abstract void DoSomething();
}
public class PurpleFlarg: FlargBase{
public PurpleFlarg()
: base(){
}
public override void DoSomething(){
// Do something here;
}
}
编译器没有给出错误或警告,但 CodeAnalysis 警告调用链包含对虚拟方法的调用,可能会产生意想不到的结果。
我很好奇,因为据我所知,可能会发生两件事。
创建基类的实例将调用没有定义实现的方法。我希望编译器会出错,或者运行时会由于缺少实现而引发异常。我假设编译器提供了 {}的实现,我错误地输入了原始代码;它确实包含类上的abstract关键字。- 创建派生类的实例将导致调用尚未实际构造的类上的方法。我本以为这会引发异常。
该代码已在生产环境中运行了几个月。它显然工作得足够正确,没有人注意到任何奇怪的行为。
我希望 StackOverflow 的杰出人才能够让我深入了解这段代码的行为和后果。
I was reviewing some code today and came across some code (accurately portrayed by this snippet)...
public abstract class FlargBase{
public FlargBase(){
this.DoSomething();
}
public abstract void DoSomething();
}
public class PurpleFlarg: FlargBase{
public PurpleFlarg()
: base(){
}
public override void DoSomething(){
// Do something here;
}
}
The compiler gives no errors or warnings, but CodeAnalysis warns that the call chain contains a call to a virtual method and may produce unintended results.
I was curious because, as I see it, two things can happen.
Creating an instance of the base class will make a call to a method with no defined implementation. I would expect the compiler to error, or the runtime to throw an exception due to a missing implementation. I'm assuming the compiler is providing an implementation of {}I mis-typed the original code; it did contain the abstract keyword on the class.- Creating an instance of a derived class will cause a call to a method on a class that has not actually been constructed yet. I would have expected this to throw an exception.
This code has been in a production environment for several months. It is apparently working correctly enough that no one has noticed any strange behavior.
I'm hoping the incredible talent here at StackOverflow can give me some insight into the behavior and consequences of this code.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
在第一个构造函数运行之前,C# 对象已完全构造并初始化为零。基本构造函数将调用虚拟方法的派生实现。
这样做被认为是不好的风格,因为当派生类的构造函数尚未被调用时,派生实现可能会表现得很奇怪。但行为本身是明确定义的。如果您在派生实现中不执行任何需要构造函数中的代码已运行的操作,那么它将起作用。
您可以想象运行时首先调用最派生的构造函数。它的第一个动作是隐式调用基本构造函数。我不确定它是否实际上是这样实现的,但由于某些 .net 语言允许您在派生构造函数的任意点调用基构造函数,因此我希望 C# 简单地调用基类构造函数作为派生构造函数的第一个操作构造函数。
这种行为与 C++ 的处理方式非常不同。在 C++ 中,派生类被一个接一个地构造,并且在派生类的构造函数启动之前,对象仍然具有基类的类型,并且派生类的重写将被忽略。
A C# object is fully constructed and initialized to zero before the first constructor runs. The base constructor will call the derived implementation of the virtual method.
It's considered bad style to do this, because the derived implementation might behave strangely when the constructor of the derived class has not been called yet. But the behavior by itself is well defined. If you do nothing in the derived implementation that requires the code from the constructor to have already run, it will work.
You can image that the runtime calls the most derived constructor first. And its first action is to implicitly call the base constructor. I'm not sure if it's actually implemented like that, but since some .net languages allow you to call the base constructor at an arbitrary point of the derived constructor, I expect C# to simply call the base class constructor as first action of the derived constructor.
This behavior is very different from how C++ handles it. In C++ the derived classes get constructed one after the other, and before the constructor of the derived class has started the object has still the type of the baseclass and the overrides from the derived class are ignored.
您的
PurpleFlarg.DoSomething()
在PurpleFlarg()
构造函数主体之前执行。这可能会导致意外,因为一般假设始终是构造函数是对对象进行操作的第一个方法。
以下是 MSDN 页面,其中包含以下示例一个“错误”条件。
Your
PurpleFlarg.DoSomething()
is executed before thePurpleFlarg()
constructor body.That can lead to surprises as the general assumption always is that the constructor is the first method to operate on an object.
Here is the MSDN page with an example of an 'error' condition.
在 C# 中,重写方法始终解析为最派生的实现。 此处为 C# 规范:
In C#, override methods always resolve to the most derived implementation. An example is given in 10.11.3 (Constructor execution) of the C# spec here:
如果类包含抽象方法(DoSomething),则该类也必须是抽象的并且不能被实例化。
If the class contains an abstract method (DoSomething), then the class has to be abstract too and cannot be instantiated.
好吧,这种模式对于现实中可重写的对象工厂确实很有用,因此在我看来,下一个代码中的情况似乎完全合法且编写得很好。
Well, this pattern is really useful for overridable factories of objects in reality, so a case like the one in the next code seems to me perfectly legal and well written.