Compiler Trivia: What is consequence of this code
Asked Answered
C

5

4

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.

Collencollenchyma answered 12/8, 2011 at 21:15 Comment(2)
I am getting an error: 'ConsoleApplication1.FlargBase.DoSomething()' is abstract but it is contained in non-abstract class 'ConsoleApplication1.FlargBase'. Also, stepping through the code when creating an instance of the derived class shows it calls DoSomething() in the derived class.Openhanded
This is closely related to the brittle base class problem. See blogs.msdn.com/b/ericlippert/archive/2004/01/07/…Demetria
B
12

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.

Basipetal answered 12/8, 2011 at 21:18 Comment(1)
You should maybe change the terminology a bit in your first sentence, since right now it is tautologically false. The object exists prior to execution of the constructor, but it is not constructed prior to being constructed.Francinefrancis
M
2

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.

Millais answered 12/8, 2011 at 21:20 Comment(0)
Z
2

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
Zygophyllaceous answered 12/8, 2011 at 21:25 Comment(2)
And how does this address the actual question?Millais
@Henk, it means that the code will never do something undefined (like throw an AV), but it may not do something you expect.Zygophyllaceous
A
0

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

Atoll answered 12/8, 2011 at 21:18 Comment(0)
B
0

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>();
    }
}
Belted answered 13/8, 2011 at 21:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.