编译器琐事:这段代码的结果是什么

发布于 2024-11-29 08:55:59 字数 742 浏览 1 评论 0原文

我今天正在审查一些代码,遇到了一些代码(此代码片段准确地描述了)...

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 警告调用链包含对虚拟方法的调用,可能会产生意想不到的结果。

我很好奇,因为据我所知,可能会发生两件事。

  1. 创建基类的实例将调用没有定义实现的方法。我希望编译器会出错,或者运行时会由于缺少实现而引发异常。我假设编译器提供了 {} 的实现,我错误地输入了原始代码;它确实包含类上的abstract关键字。
  2. 创建派生类的实例将导致调用尚未实际构造的类上的方法。我本以为这会引发异常。

该代码已在生产环境中运行了几个月。它显然工作得足够正确,没有人注意到任何奇怪的行为。

我希望 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.

  1. 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.
  2. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

剧终人散尽 2024-12-06 08:55:59

在第一个构造函数运行之前,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.

深海里的那抹蓝 2024-12-06 08:55:59

您的 PurpleFlarg.DoSomething()PurpleFlarg() 构造函数主体之前执行。

这可能会导致意外,因为一般假设始终是构造函数是对对象进行操作的第一个方法。

以下是 MSDN 页面,其中包含以下示例一个“错误”条件。

Your PurpleFlarg.DoSomething() is executed before the PurpleFlarg() 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.

你怎么这么可爱啊 2024-12-06 08:55:59

在 C# 中,重写方法始终解析为最派生的实现。 此处为 C# 规范

变量初始值设定项被转换为赋值语句,并且
这些赋值语句在调用之前执行
基类实例构造函数。此顺序确保所有
实例字段之前由其变量初始值设定项初始化
任何有权访问该实例的语句都会被执行。

举个例子

使用系统;

A级
{
   公共 A() {
      打印字段();
   }

   公共虚拟无效PrintFields(){}

}

B级:A
{
   整数x=1;
   整数y;

   公共 B() {
      y = -1;
   }

   公共覆盖无效PrintFields(){
      Console.WriteLine("x = {0}, y = {1}", x, y);
   }
}

当使用new B()创建B的实例时,输出如下
生产:

<前><代码>x = 1,y = 0

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:

Variable initializers are transformed into assignment statements, and
these assignment statements are executed before the invocation of the
base class instance constructor. This ordering ensures that all
instance fields are initialized by their variable initializers before
any statements that have access to that instance are executed.

Given the example

using System;

class A
{
   public A() {
      PrintFields();
   }

   public virtual void PrintFields() {}

}

class B: A
{
   int x = 1;
   int y;

   public B() {
      y = -1;
   }

   public override void PrintFields() {
      Console.WriteLine("x = {0}, y = {1}", x, y);
   }
}

when new B() is used to create an instance of B, the following output
is produced:

x = 1, y = 0
还如梦归 2024-12-06 08:55:59

如果类包含抽象方法(DoSomething),则该类也必须是抽象的并且不能被实例化。

If the class contains an abstract method (DoSomething), then the class has to be abstract too and cannot be instantiated.

隱形的亼 2024-12-06 08:55:59

好吧,这种模式对于现实中可重写的对象工厂确实很有用,因此在我看来,下一个代码中的情况似乎完全合法且编写得很好。

abstract class MyBase
{
    public object CustomObject { get; private set; }

    public MyBase()
    {
        this.CustomObject = this.CreateCustomObject();
    }

    protected abstract object CreateCustomObject();
}

class MyBaseList : MyBase
{
    protected override object CreateCustomObject()
    {
        return new List<int>();
    }
}

class MyBaseDict : MyBase
{
    protected override object CreateCustomObject()
    {
        return new Dictionary<int, int>();
    }
}

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.

abstract class MyBase
{
    public object CustomObject { get; private set; }

    public MyBase()
    {
        this.CustomObject = this.CreateCustomObject();
    }

    protected abstract object CreateCustomObject();
}

class MyBaseList : MyBase
{
    protected override object CreateCustomObject()
    {
        return new List<int>();
    }
}

class MyBaseDict : MyBase
{
    protected override object CreateCustomObject()
    {
        return new Dictionary<int, int>();
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文