在Delphi中将对象转换为父类

OwK*_*OwK 1 delphi class parent

给定以下类层次结构:

TClass1 = class
end;

TClass2a = class(TClass1)
end;

TClass2b = class(TClass1)
end;
Run Code Online (Sandbox Code Playgroud)

我使用以下重载程序对它们进行操作

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
Run Code Online (Sandbox Code Playgroud)

如何动态地将每个参数转换为其父类,而不是硬编码?
我想替换这个:

// Do something as parent class
DoSomething(TClass1(AObj))
Run Code Online (Sandbox Code Playgroud)

有一些更通用的东西,像这样

// Do something as parent class
DoSomething(AObj.ClassParent(AObj))
Run Code Online (Sandbox Code Playgroud)



更新:在这种情况下,DoSomething过程必须位于类层次结构之外.我不能反转结构,所以我不能利用类继承和多态.
此外,这只是一个例子.我想回答关注核心问题:如何在运行时将对象转换为其父类.

Dav*_*nan 6

在这种情况下,DoSomething过程必须位于类层次结构之外.我不能反转结构,所以我不能利用类继承和多态.此外,这只是一个例子.我想关注核心问题:如何在运行时将对象转换为其父类.

理解这一点的关键是Delphi是一种静态类型语言.还要记住,您正在调用非多态过程.这意味着参数的类型在编译时确定.并且重载决策基于该类型.因此,重载解析在编译时发生.

那么,你的例子:

DoSomething(TClass1(AObj))
Run Code Online (Sandbox Code Playgroud)

做你想要的,因为参数的类型在编译时是已知的.根本不可能做出类似的东西

DoSomething(AObj.ClassParent(AObj))
Run Code Online (Sandbox Code Playgroud)

做你想做的事,因为必须在编译时知道参数的类型.

如何在运行时将对象强制转换为其父类?

这是问题的症结所在.Casting不是运行时构造,它是一个编译时构造.所以简单的答案是你不能将对象强制转换为它的运行时类型.

如果你不能使用多态分派,那么你唯一的选择是硬编码的强制转换.Cosmin的答案中的例子展示了如何以一种非常有用的方式做到这一点,但事实仍然是在编译时解决了重载.根本没有办法逃避这一点.


您在评论中询问RTTI是否对您有所帮助.好吧,它已经无法帮助您解决已经讨论过的任何转换或重载问题.但是,它可以帮助您避免许多样板硬编码演员表.这是一个简单的例子:

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.
Run Code Online (Sandbox Code Playgroud)

输出继电器:

DoSomething_TClass1
DoSomething_TClass1
DoSomething_TClass2a
DoSomething_TClass1
DoSomething_TClass2b

显然,这种方法依赖于您遵循命名约定.

与硬编码的转换变体相比,这种方法的主要优点之一是调用继承方法的顺序由类层次结构决定.

  • RTTI代表运行时类型信息.正如我解释的那样,重载方法解析在编译时发生.RTTI无法帮助您. (3认同)