6

基本的に、クラスが同じジェネリック インターフェイスの 2 つの異なるバージョンを実装できるようにしたいと考えています。

このコードを検討してください

type
  // a generic interface
  ITest<T> = interface
    ['{6901FE04-8FCC-4181-9E92-85B73264B5DA}']
    function Val: T;
  end;

  // a class that purports to implement two different types of that interface
  TTest<T1, T2> = class(TInterfacedObject, ITest<T1>, ITest<T2>)
  protected
    fV1: T1;
    fV2: T2;
  public
    constructor Create(aV1: T1; aV2: T2);
    function Val: T1;               // Val() for ITest<T1>
    function T2Val: T2;             // Val() for ITest<T2>
    function ITest<T2>.Val = T2Val; // mapping
  end;

constructor TTest<T1, T2>.Create(aV1: T1; aV2: T2);
begin
  inherited Create;
  fV1 := aV1;
  fV2 := aV2;
end;

function TTest<T1, T2>.T2Val: T2;
begin
  result := fV2;
end;

function TTest<T1, T2>.Val: T1;
begin
  result := fV1;
end;

/////////////
procedure Test;
var
  t : TTest<integer, string>;
begin
  t := TTest<integer, string>.Create(39, 'Blah');
  ShowMessage((t as ITest<string>).Val);            // this works as expected
  ShowMessage(IntToStr((t as ITest<integer>).Val)); // this gets AV
end;

最初の ShowMessage は予想どおり「Blah」を表示しますが、2 番目の ShowMessage はクラッシュします。クラッシュする理由は、呼び出しが予想どおり Val() ではなく T2Val() を呼び出すためです。どうやら、競合解決マッピングは、ITest: T2 だけでなく、両方のタイプのインターフェイスのメソッドをマップします。

それで、ここに私の質問があります。

これはバグですか?つまり、Embarcadero はこれをサポートすることを意図していたのに、単純に間違って実装したのでしょうか? それとも、プログラマーにこのようなことをさせるつもりはまったくなかったのでしょうか? (正直なところ、テスト プログラムがコンパイルされたことに少し驚きました)

これがバグである場合、1 つのクラスで 2 つの異なるタイプの単一のジェネリック インターフェイスをサポートできるようにするための回避策があるかどうか、誰にもわかりませんか?

4

2 に答える 2

11

asインターフェイス タイプを使用すると、GUID を使用してインターフェイスを検索するインターフェイス キャストが使用されます。GUID を持つ汎用インターフェイスの場合、すべてのインスタンスが同じ GUID を取得します。1 つの型がインターフェイスの複数のコピーを実装している場合、GUID で検索すると、最初のインターフェイスが返されます。

インターフェイス キャストを使用せず、代わりに次のようなインターフェイス変換を使用すると、プログラムは期待どおりに動作します。

procedure Test;
var
  t : TTest<integer, string>;
begin
  t := TTest<integer, string>.Create(39, 'Blah');
  ShowMessage(ITest<string>(t).Val);
  ShowMessage(IntToStr(ITest<Integer>(t).Val));
end;

もともと、ジェネリックが Win32 用に実装されていたとき、GUID はジェネリック インターフェイスで許可されていませんでした。ただし、ジェネリック インターフェイスの動的クエリは、ジェネリック コンテナーのシナリオでは望ましいものであり、一般的には、アルゴリズムのコンテキストで型固有のサービスについてサービス プロバイダーにクエリを実行するためのメカニズムとして望ましいものでした (並べ替えや検索など、比較子や比較子などを必要とします)。等式テスト)。そのため、新しい計画が形成されました。ジェネリック インターフェイスに GUID を設定しますが、ジェネリック インスタンス化の型引数のハッシュを作成し、ハッシュを GUID にフォールド (xor など) して、個別の互換性のないインスタンス化ごとに一意の GUID を作成します。しかし、これは遅刻であり、時間の制約内で適切な実装ができませんでした。ただし、動的クエリの要件は残っていたため、GUID はそのままでした。それか'

特定のシナリオを解決するには、明示的な GUID を持つ個別の子孫を使用することをお勧めします。または、インターフェイスのクエリに別のメカニズムを使用します。

于 2009-11-12T01:07:48.553 に答える
3

これは興味深い問題です。発生しているように見えるのは、コンパイラが常にインターフェイスを最後に指定されたバージョンのインターフェイスにマッピングしていることです (順序を入れ替えると、他のメソッドが呼び出されます)。これは、両方のインターフェイスが同じ GUID シグネチャを持っているという事実に関係している可能性があるため、ディスパッチャーは、インターフェイス呼び出しを確認したときにどのメソッドを呼び出す必要があるかについて混乱しています。

これはバグであると思われるため、Quality Central から報告する必要があります。

于 2009-11-12T00:44:22.343 に答える