OwK
OwK

Reputation: 53

Cast an object to parent class in Delphi

Given the following class hierarchy:

TClass1 = class
end;

TClass2a = class(TClass1)
end;

TClass2b = class(TClass1)
end;

I operate on them using the following overloaded procedures

procedure DoSomething(AObj : TClass1); overload;
begin
  // ...Do something for TClass1
end

procedure DoSomething(AObj : TClass2a); overload;
begin
  // Do something as parent class
  DoSomething(TClass1(AObj))

  // ...Do something for TClass2a
end

procedure DoSomething(AObj : TClass2b); overload;
begin
  // Do something as parent class
  DoSomething(TClass1(AObj))

  // ...Do something for TClass2b
end

How can I dynamically cast each parameter to its parent class, instead of hard coding it?
I'd like to replace this:

// Do something as parent class
DoSomething(TClass1(AObj))

With something more generic, like this

// Do something as parent class
DoSomething(AObj.ClassParent(AObj))



Update: DoSomething procedures must reside outside the class hierarchy in this case. I can't reverse the structure, so I can't take advantage of class inheritance and polymorphism.
Furthermore, this is just an example. I'd like answers to focus on the core question: how can I cast an object to its parent class at runtime.

Upvotes: 1

Views: 2817

Answers (3)

kludg
kludg

Reputation: 27493

Nobody mentioned yet virtual class methods. Though the literal answer to your question is still "no, that is not possible" you can write code like that:

type
  TClass1 = class
  end;

  TClass2a = class(TClass1)
  end;

  TClass2b = class(TClass1)
  end;

type
  TSomething1 = class
    class procedure DoSomething(AObj : TClass1); virtual;
  end;

  TSomething2a = class(TSomething1)
    class procedure DoSomething(AObj : TClass1); override;
  end;

  TSomething2b = class(TSomething1)
    class procedure DoSomething(AObj : TClass1); override;
  end;

{ TSomething1 }

class procedure TSomething1.DoSomething(AObj: TClass1);
begin
  ShowMessage(AObj.ClassName);
end;

{ TSomething2a }

class procedure TSomething2a.DoSomething(AObj: TClass1);
begin
  inherited;
  ShowMessage('2A');
end;

{ TSomething2b }

class procedure TSomething2b.DoSomething(AObj: TClass1);
begin
  inherited;
  ShowMessage('2B');
end;

Upvotes: 2

David Heffernan
David Heffernan

Reputation: 612794

DoSomething procedures must reside outside the class hierarchy in this case. I can't reverse the structure, so I can't take advantage of class inheritance and polymorphism. Furthermore, this is just an example. I'd like to focus on the core question: how can I cast an object to its parent class at runtime.

The key to understanding this is the fact that Delphi is a statically typed language. Remember also that you are calling non-polymorphic procedures. Which means that the type of the parameter is determined at compile time. And the overload resolution is based on that type. So, overload resolution happens at compile time.

So, your example:

DoSomething(TClass1(AObj))

does what you want because the type of the parameter is known at compile time. It's simply not possible to make anything like

DoSomething(AObj.ClassParent(AObj))

do what you want because the type of the parameter has to be known at compile time.

How can I cast an object to its parent class at runtime?

This is the crux of the matter. Casting is not a runtime construct, it is a compile time construct. So the simple answer is that you cannot cast an object to its runtime type.

If you cannot use polymorphic dispatch then your only option is hard-coded casts. The example in Cosmin's answer shows how to do that in a quite usable fashion but the fact remains that overloads are resolved at compile time. There's simply no way to escape that.


You ask in a comment if RTTI can be helpful here. Well, it won't help you with any casting or overload resolution as already discussed. However, it can help you avoid lots of boilerplate hard coded casts. Here's a simple example:

program Project1;

{$APPTYPE CONSOLE}

uses
  System.TypInfo,System.Rtti;

type
  TClass1 = class
  end;
  TClass1Class = class of TClass1;

  TClass2a = class(TClass1)
  end;

  TClass2b = class(TClass1)
  end;

type
  TClass1Dispatcher = class
  private
    class var Context: TRttiContext;
  public
    class procedure DoSomething_TClass1(AObj: TClass1);
    class procedure DoSomething_TClass2a(AObj: TClass2a);
    class procedure DoSomething_TClass2b(AObj: TClass2b);
    class procedure DoSomething(AObj: TClass1; AClass: TClass1Class); overload;
    class procedure DoSomething(AObj: TClass1); overload;
  end;

class procedure TClass1Dispatcher.DoSomething_TClass1(AObj: TClass1);
begin
  Writeln('DoSomething_TClass1');
end;

class procedure TClass1Dispatcher.DoSomething_TClass2a(AObj: TClass2a);
begin
  Writeln('DoSomething_TClass2a');
end;

class procedure TClass1Dispatcher.DoSomething_TClass2b(AObj: TClass2b);
begin
  Writeln('DoSomething_TClass2b');
end;

class procedure TClass1Dispatcher.DoSomething(AObj: TClass1; AClass: TClass1Class);
var
  LType: TRttiType;
  LMethod: TRttiMethod;
begin
  if AClass<>TClass1 then
    DoSomething(AObj, TClass1Class(AClass.ClassParent));
  LType := Context.GetType(TypeInfo(TClass1Dispatcher));
  LMethod := LType.GetMethod('DoSomething_'+AClass.ClassName);
  LMethod.Invoke(Self, [AObj]);
end;

class procedure TClass1Dispatcher.DoSomething(AObj: TClass1);
begin
  DoSomething(AObj, TClass1Class(AObj.ClassType));
end;

begin
  TClass1Dispatcher.DoSomething(TClass1.Create);
  TClass1Dispatcher.DoSomething(TClass2a.Create);
  TClass1Dispatcher.DoSomething(TClass2b.Create);
  Readln;
end.

Ouput:

DoSomething_TClass1
DoSomething_TClass1
DoSomething_TClass2a
DoSomething_TClass1
DoSomething_TClass2b

Obviously such an approach relies on you following the naming convention.

One of the main benefits of this approach over the hard-coded casting variants is that the order of calling the inherited methods is determined by the class hierarchy.

Upvotes: 6

Cosmin Prund
Cosmin Prund

Reputation: 25678

overload is only useful at compile-time, it allows the compiler to select the most appropriate method based on the type of the object that's passed as a parameter. You can't use the overload mechanism at run time to dynamically make the call because, by that time, the code has been compiled and the one overloaded procedure already chosen. For all you know the proper method to call (based on "overloaded" logic) might not even be available: If the compiler never chose the overloaded method for anything, the linker might have discarded. Because of this you can't use RTTI, because the method might simply NOT EXIST in the executable, unless you already hard-coded a call to it.

Your only choice is to do some hard coding. I'd create a method that takes two parameters, the object to operate on and a TClass parameter, something like this:

procedure Dispatcher(Obj: TClass1);
var AsClass: TClass;
begin
  AsClass := Obj.ClassType;
  while Assigned(AsClass) do
  begin
    // Hard-coded dispatch for the type in AsClass.
    if AsClass.InheritsFrom(TClass3) then
      DoSomething(TClass3(Obj))
    else if AsClass.InheritsFrom(TClass2) then
      DoSomething(TClass2(Obj))
    else if AsClass.InheritsFrom(TClass1) then
      DoSomething(TClass1(Obj));

    // This emulates the "inherited" call in normal polymorphic OOP.
    // We're simply recursively calling the dispatcher for the parent of AsClass.
    AsClass := AsClass.ClassParent;
  end;
end;

Given an object of type TClass3, this procedure will call DoSomething 3 times, once for each level of inheritance. And it will choose the proper overloaded version because of the hard-coded cast.

Sample code:

var X1: TClass1;
begin
  X1 := TClass3.Create;
  Dispatcher(X1); // This will call all 3 versions of DoSomething, in order.
end;

Since the code doesn't really use the overloaded keyword for anything useful, I'd drop it's use, give distinct names to all methods so the code in the Dispatcher method looks like this:

procedure Dispatcher(Obj: Tobject);
var AsClass: TClass;
begin
  AsClass := Obj.ClassType;
  if AsClass.InheritsFrom(TClass3) then
    DoSomething_Class3(TClass3(Obj))
  else if AsClass.InheritsFrom(TClass2) then
    DoSomething_Class2(TClass2(Obj))
  else if AsClass.InheritsFrom(TClass1) then
    DoSomething_Class1(TClass1(Obj));

  if AsClass.ClassParent <> nil then
    Dispatcher(Obj, AsClass.ClassParent);
end;

This variant is safer in the long-run because it doesn't depend on compiler-magic. For example, in the first variant, if you decide to drop the overloaded procedure that works for a parameter of type TClass2 but you'd forget to drop the call with the TClass2() cast in the Dispatcher, you'd get two calls for the overloaded method that takes the TClass1 parameter, because that would be the best match for the now useles:

DoSomething(TClass2(Obj))

Upvotes: 3

Related Questions