4

私は、同じ基本クラスで定義された仮想メソッドをオーバーライドすることによって、同じ基本クラスから継承する一連のライブラリクラスを拡張しようとしています。変更は常に同じであるため、ライブラリクラスのN個のサクセサを作成する代わりに、ライブラリクラスタイプによってパラメータ化されたジェネリッククラスを作成することにしました。これは、パラメータで指定されたクラスから継承し、基本クラスのメソッドをオーバーライドします。問題は、以下のコードがコンパイルされず、コンパイラがTからの継承を許可しないことです。

program Project1;

type
  LibraryBaseClass = class
    procedure foo; virtual;
  end;

  LibraryClassA = class(LibraryBaseClass)
  end;

  LibraryClassB = class(LibraryBaseClass)
  end;

  LibraryClassC = class(LibraryBaseClass)
  end;

  LibraryClassD = class(LibraryBaseClass)
  end;

  MyClass<T:LibraryBaseClass> = class(T) //Project1.dpr(20) Error: E2021 Class type required
    procedure foo; override;
  end;

procedure LibraryBaseClass.foo; 
begin
end;

procedure MyClass<T>.foo; 
begin
end;

begin
  MyClass<LibraryClassA>.Create.foo;
  MyClass<LibraryClassB>.Create.foo;
  MyClass<LibraryClassC>.Create.foo;
  MyClass<LibraryClassD>.Create.foo;
end.

これを機能させる方法はありますか?Dictionary<T,T>たとえば、問題なくコンパイルから継承するため、コンパイラをだまして同等のものを受け入れる方法があるかもしれません。

それとも、私と同じ目標を持っていたらどうしますか?実際の状況では、複数のメソッドをオーバーライドして、いくつかのデータメンバーを追加する必要があることに注意してください。

ありがとうございました

4

4 に答える 4

9

すでにお話ししたように、これはC#やDelphiのジェネリックではなく、C++テンプレートで有効です。テンプレートとジェネリックスの基本的な違いは、概念的には、各テンプレートのインスタンス化は完全に個別にコンパイルされたタイプであるということです。ジェネリックは、考えられるすべてのタイプに対して1回コンパイルされます。タイプパラメータから派生する場合、次のような構造を取得できるため、これは単純に不可能です。

type
  LibraryBaseClass = class
    procedure foo; virtual;
  end;

  LibraryClassA = class(LibraryBaseClass)
    procedure foo; reintroduce; virtual;
  end;

  LibraryClassB = class(LibraryBaseClass)
  end;

  MyClass<T:LibraryBaseClass> = class(T)
    procedure foo; override; // overrides LibraryClass.foo or LibraryClassA.foo ?
  end;

ただし、これはC ++でも機能します。これは、C ++ではMyClass<LibraryClassA>完全MyClass<LibraryClassB>に分離されており、インスタンス化するときMyClass<LibraryClassA>に、基本クラスのメソッドが見つかる前にfoo検索されて検出されるためです。LibraryClassA

それとも、私と同じ目標を持っていたらどうしますか?実際の状況では、複数のメソッドをオーバーライドして、いくつかのデータメンバーを追加する必要があることに注意してください。

実行時に型を作成することは可能ですが、ほぼ確実に非常に悪い考えです。私は一度それを利用しなければならなかったので、それを避けたかったでしょう。これには、VMTの読み取り、そのコピーの作成、元のメソッドポインターのコピーの保存、LibraryBaseClass.fooカスタムメソッドを指すようにVMTの変更、およびそのオーバーライド関数からの元の保存されたメソッドポインターの呼び出しが含まれます。確かに、組み込みの言語サポートはなく、コードから派生型を参照する方法もありません。

後でC#でもこれが必要になったことがありますが、その場合、可能な基本クラスが4つしかないのは幸運でした。最終的に、4つの個別の派生クラスを手動で作成し、メソッドを4回実装し、ルックアップ構造(Dictionary<,>)を使用して正しい基本クラスを正しい派生クラスにマップしました。

あなたの質問には当てはまらないが、他の読者を助けるかもしれない特定のケースのためのトリックがあることに注意してください:あなたの派生クラスがすべて同じインターフェースを実装する必要があり、新しいデータメンバーや関数オーバーライドを必要としない場合、あなたは書くことを避けることができます複数回の実装:

type
  IMySpecialInterface = interface
    procedure ShowName;
  end;

  TMySpecialInterfaceHelper = class helper for TComponent
    procedure ShowName;
  end;

procedure TMySpecialInterfaceHelper.ShowName;
begin
  ShowMessage(Name);
end;

type
  TLabelWithShowName = class(TLabel, IMySpecialInterface);
  TButtonWithShowName = class(TButton, IMySpecialInterface);

その場合、クラスヘルパーメソッドの実装は、インターフェイスメソッドの有効な実装になります。

于 2012-09-04T14:12:09.907 に答える
4

Delphi XE以降では、まったく異なるものを試すこともできますTVirtualMethodInterceptor

于 2012-09-04T18:09:09.227 に答える
3

あなたがやろうとしていることは、Delphi ジェネリックでは不可能です。

それだけの価値があるため、同等のコードは C# ジェネリックでも無効です。ただし、デザインは C++ テンプレートで機能します。

于 2012-09-04T13:59:50.520 に答える
0

私はおそらく問題の説明を誤解していましたが、単純化された例から、「それを好転させ」、次のように階層の真ん中にクラスを挿入できるようです:

program Project1;

type
  LibraryBaseClass = class
    procedure foo; virtual;
  end;

  LibraryBaseFooClass = class(LibraryBaseClass)
    procedure foo; override;
  end;

  LibraryClassA = class(LibraryBaseFooClass)
  end;

  LibraryClassB = class(LibraryBaseFooClass)
  end;

  LibraryClassC = class(LibraryBaseFooClass)
  end;

  LibraryClassD = class(LibraryBaseFooClass)
  end;

procedure LibraryBaseClass.foo; 
begin
end;

procedure LibraryBaseFooClass.foo; 
begin
end;

begin
  LibraryClassA.Create.foo;
  LibraryClassB.Create.foo;
  LibraryClassC.Create.foo;
  LibraryClassD.Create.foo;
end.
于 2012-09-04T14:13:29.640 に答える