1

次のクラス階層があるとします。

TClass1 = class
end;

TClass2a = class(TClass1)
end;

TClass2b = class(TClass1)
end;

次のオーバーロードされた手順を使用してそれらを操作します

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

ハードコーディングする代わりに、各パラメータをその親クラスに動的にキャストするにはどうすればよいですか?
これを置き換えたい:

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

このようなより一般的なもので

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



更新:この場合、DoSomething プロシージャはクラス階層の外に存在する必要があります。構造を逆にすることはできないので、クラス継承とポリモーフィズムを利用できません。
さらに、これはほんの一例です。中心的な質問に焦点を当てた回答を希望します。実行時 にオブジェクトをその親クラスにキャストするにはどうすればよいですか。

4

3 に答える 3

6

この場合、DoSomething プロシージャはクラス階層の外に存在する必要があります。構造を逆にすることはできないので、クラス継承とポリモーフィズムを利用できません。さらに、これはほんの一例です。ここでは、実行時にオブジェクトをその親クラスにキャストするにはどうすればよいかという中心的な質問に焦点を当てたいと思います。

これを理解する鍵は、Delphi が静的に型付けされた言語であるという事実です。また、非ポリモーフィック プロシージャを呼び出していることにも注意してください。つまり、パラメーターの型はコンパイル時に決定されます。そして、オーバーロードの解決はその型に基づいています。したがって、オーバーロードの解決はコンパイル時に行われます。

だから、あなたの例:

DoSomething(TClass1(AObj))

コンパイル時にパラメーターの型がわかっているため、必要なことを行います。次のようなものを作ることは単に不可能です

DoSomething(AObj.ClassParent(AObj))

コンパイル時にパラメーターの型を知る必要があるため、必要なことを行います。

実行時にオブジェクトをその親クラスにキャストするにはどうすればよいですか?

これが問題の核心です。キャストは実行時の構造ではなく、コンパイル時の構造です。したがって、簡単な答えは、オブジェクトをそのランタイム型にキャストできないということです。

ポリモーフィック ディスパッチを使用できない場合は、ハードコーディングされたキャストしか選択肢がありません。Cosmin's answer の例は、非常に使いやすい方法でそれを行う方法を示していますが、コンパイル時にオーバーロードが解決されるという事実は残っています。それを逃れる方法はまったくありません。


ここで 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.

出力:

DoSomething_TClass1
DoSomething_TClass1
DoSomething_TClass2a
DoSomething_TClass1
DoSomething_TClass2b

明らかに、このようなアプローチは、命名規則に従うことに依存しています。

ハードコーディングされたキャスト バリアントに対するこのアプローチの主な利点の 1 つは、継承されたメソッドを呼び出す順序がクラス階層によって決定されることです。

于 2012-10-05T09:05:51.373 に答える
3

overloadコンパイル時にのみ有用です。これにより、コンパイラは、パラメーターとして渡されたオブジェクトの型に基づいて最も適切なメソッドを選択できます。overload実行時にこのメカニズムを使用して動的に呼び出しを行うことはできません。これは、その時点までにコードがコンパイルされ、1 つのオーバーロードされたプロシージャが既に選択されているためです。(「オーバーロードされた」ロジックに基づいて)呼び出す適切なメソッドが利用できない可能性があることを知っている限り、コンパイラーがオーバーロードされたメソッドを何も選択しなかった場合、リンカーは破棄した可能性があります。このため、RTTI を使用することはできません。メソッドへの呼び出しを既にハードコーディングしていない限り、メソッドが実行可能ファイルに単に存在しない可能性があるためです。

唯一の選択肢は、ハードコーディングを行うことです。操作対象のオブジェクトとパラメーターの 2 つのパラメーターを受け取るメソッドを作成します。たとえば、次のTClassようになります。

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;

type のオブジェクトを指定TClass3すると、このプロシージャはDoSomething継承のレベルごとに 1 回ずつ、合計 3 回呼び出されます。また、キャストがハードコーディングされているため、オーバーロードされた適切なバージョンが選択されます。

サンプルコード:

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

コードでは実際にはキーワードを有用な目的で使用していないため、overloaded使用を中止し、すべてのメソッドに個別の名前を付けて、Dispatcher メソッドのコードが次のようになるようにします。

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;

このバリアントは、コンパイラの魔法に依存しないため、長期的にはより安全です。たとえば、最初のバリアントでは、タイプのパラメーターに対して機能するオーバーロードされたプロシージャを削除することに決めたが、Dispatcher でキャストをTClass2使用して呼び出しを削除するのを忘れた場合、オーバーロードされたメソッドに対して 2 つの呼び出しが発生します。これは、現在使用されているものに最適であるためTClass2()、パラメーターを取ります。TClass1

DoSomething(TClass2(Obj))
于 2012-10-05T09:34:49.373 に答える
2

仮想クラス メソッドについてはまだ誰も言及していません。あなたの質問に対する文字通りの答えはまだ「いいえ、それは不可能です」ですが、次のようなコードを書くことができます:

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;
于 2012-10-05T11:54:07.843 に答える