3
votes

How to specify overridden method of which base class to call in Delphi?

Let say, a line of inheritance like this: TObject -> ... SomeMoreBaseClass ... -> ParentClass -> MyClass

Suppose ParentClass has no Create(), but it has a Create(int = 0). Such that when you call ParentClass.Create(), it actually calls ParentClass.Create(0)

Now, in the constructor Create() of MyClass, if I call "inherited;", I found that I am not getting the ParentClass.Create(0), instead I am getting .Create() of the Base Classes or even TObject.

So, how can I make it to call ParentClass.Create()?

The easiest would be "inherited Create(0)", but it doesn't feel "right" enough.

(The ParentClass in my case is actually the System.Generics.Collections.TDictionary)

Code sample:

type

  TParentClass = class
  public
    constructor Create(n:Integer = 0);
  end;

  TDerivedClass = class(TParentClass)
  public
    constructor Create; // Note: no parameters
  end;

constructor TDerivedClass.Create;
begin
  // inherited; // this calls TObject.Create, not TParentClass.Create(0);
  inherited Create(0);
end;
2
TObject does not have Create constructor. Are you sure you have override on all inherited constructors ? Parameterless constructor Create() will not be called automatically when constructor with parameters Create(Parameter: Integer) was declared in some inherited classes. You need to call inherited Create() in first (base) Create(Parameter: Integer);too
@too TObject indeed has a parameterless constructor Create.David Heffernan
This question desperately needs a code sample.Cosmin Prund
@David Heffernan: Sorry for a mind shortcut, I meant virtual constructor.too

2 Answers

12
votes

First of all, as @Cosmin explains in some detail, the question does not concern overridden methods. The question is about calling inherited methods.

inherited Create;

is the best you can do here. This calls the TDictionary<TKey,TValue> constructor passing the default ACapacity of 0.

In fact it may even be preferable to write:

inherited Create(0);

and be quite explicit.


My assumption is that your code looks like this:

type
  TMyClass<TKey,TValue> = class(TDictionary<TKey,TValue>)
  public
    constructor Create;
  end;

constructor TMyClass<K,V>.Create;
begin
  inherited;
end;

I read the documentation for the inherited keyword in an attempt to understand the difference between inherited and inherited Create. The best clues are contained in the following excerpts:

If inherited is followed by the name of a member, it represents a normal method call ...

and

When inherited has no identifier after it, it refers to the inherited method with the same name as the enclosing method. In this case, inherited takes no explicit parameters, but passes to the inherited method the same parameters with which the enclosing method was called.

This seems to hint that the two competing uses of inherited are treated differently.

My understanding is that inherited results in a call to a constructor with matching parameters. In your case, TMyClass<K,V>.Create is parameterless and so the only matching constructor is that of TObject. Note that none of the constructors of TDictionary can match since they all take parameters.

On the other hand, when you write inherited Create this is a normal method call. And so default parameters can be added to the method call. The crucial point is that this variant allows calling inherited methods with non-matching parameter lists.

In my view, the inherited syntax with no following identifier should have been reserved for virtual methods.


The designers of TDictionary<TKey,TValue> could have saved you from this unhappy fate. The constructors of TDictionary<TKey,TValue> should have been implemented like this:

constructor Create; overload;
constructor Create(ACapacity: Integer); overload;
.....other constructors omitted

Then the implementation for the parameterless constructor would simply be:

constructor TDictionary<TKey,TValue>.Create;
begin
  Create(0);
end;

Had this decision been taken, the parameterless constructor declared in TObject would have been hidden from any derived classes and your code would work as you intended.

The problem you have encountered here is the result of an unhappy confluence of events involving overloading, default parameters, the parameterless constructor of TObject and the quirky syntax of inherited for constructors. Whilst writing inherited is highly readable and concise, it simply leads to confusion when overloaded methods are in play.

6
votes

First of all this is a bad example: TDictionary.Create is not a virtual constructor so you're not actually overriding it. You're simply re-introducing it in the new class. This is actually a good thing because you can use tricks to call a non-virtual method from whatever base class you want. You can simply use something like this:

TBaseClass(Self).NonVirtualMethodName(Parameters).

or in your case:

constructor TMyDerived.Create;
begin
  TDictionary<T>(Self).Create; // cast and call the constructor you want.
end;

A constructor can be called as a normal method, and Delphi allows this by design. None the less constructors are special methods. Even if you can use the cast trick to call any constructor you want, you shouldn't do that: It "breaks OOP": what if your immediate parent depends on something that's going on in it's own constructor? You're not supposed to know or care what the ancestor class does in its constructor.

I mentioned TDictionary.Create is not a virtual constructor, here's why. There's a fundamental difference in the way virtual and non-virtual methods are called. Virtual methods are called through a "virtual method table", you'll always get the method for the object that's actually instantiated. Non-virtual methods are resolved at compile time. In the following example, you'll notice both X and Y are instantiated using the same object class TSecondChild, yet when calling the NonVirtual method the outcome is different depending on the type of the variable. Not so for the VMethod, that's a virtual method and the correct method is always called.

This has implications for virtual constructors, since you're talking about Constructors. If for example you'd do something like this, you'd end up with a infinite recursive loop:

constructor TDemoClass.Create; // where Create is VIRTUAL, not the case with TDictionary
begin
  // I'm "smart", I don't call Inherited
  TBaseClass(Self).Create; // Ooops, I just called myself! Recursive loop!
end;

Here's the demo console app that demonstrates the difference between virtual and non-virtual methods and how you can call the inherited methods:

program Project13;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type

  TBase = class
  public
    procedure NonVirtual;
    procedure VMethod(N:Integer);virtual;
  end;

  TFirstChild = class(TBase)
  public
    procedure NonVirtual;
    procedure VMethod(N:Integer);override;
  end;

  TSecondChild = class(TFirstChild)
  public
    procedure NonVirtual;
    procedure VMethod(N:Integer);override;
  end;

{ TBase }

procedure TBase.NonVirtual;
begin
  WriteLn('TBase.NonVirtual');
end;

procedure TBase.VMethod(N:Integer);
begin
  WriteLn('TBase.VMethod');
end;

{ TFirstChild }

procedure TFirstChild.NonVirtual;
begin
  WriteLn('TFirstChild.NonVirtual');
end;

procedure TFirstChild.VMethod(N:Integer);
begin
  WriteLn('TFirstChild.VMethod');
end;

{ TSecondChild }

procedure TSecondChild.NonVirtual;
begin
  WriteLn('TSecondChild.NonVirtual');
  TBase(Self).NonVirtual;
end;

procedure TSecondChild.VMethod(N:Integer);
begin
  WriteLn('TSecondCHild.VMethod, N=', N);
  if N > 0 then // This stops infinite recursion
    TBase(Self).VMethod(N-1);
end;

var X: TFirstChild;
    Y: TSecondChild;

begin
  try

    WriteLn('Calling through a variable of type TFirstChild');

    X := TSecondChild.Create;
    X.NonVirtual; // Writes TFirstChild.NonVirtual
    X.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times

    WriteLn;
    WriteLn('Calling through a variable of type TSecondChild');

    Y := TSecondChild.Create;
    Y.NonVirtual; // Writes TSecondChild.NonVirtual
    Y.VMethod(2); // Writes TSecondChild.NonVirtual, 3 times

    WriteLn;
    WriteLn('Press ENTER');
    Readln;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.