次のインターフェイスとそれらを実装するクラスがある場合-
IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}']
End;
IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}']
End;
TImplementation = Class(TInterfacedObject, IDerived)
End;
次のコードは「Bad!」を出力します -
Procedure Test;
Var
A : IDerived;
Begin
A := TImplementation.Create As IDerived;
If Supports (A, IBase) Then
WriteLn ('Good!')
Else
WriteLn ('Bad!');
End;
これは少し面倒ですが理解できます。IBaseはTImplementationがサポートするGUIDのリストに含まれていないため、サポートをIBaseにキャストできません。宣言を-に変更することで修正できます。
TImplementation = Class(TInterfacedObject, IDerived, IBase)
それでも、それをしなくても、AはIDerivedであり、IDerivedはIBaseであるため、AがIBaseを実装していることはすでに知っています。したがって、チェックを省略した場合、Aをキャストでき、すべてが正常になります-
Procedure Test;
Var
A : IDerived;
B : IBase;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
//Can now successfully call any of B's methods
End;
しかし、IBasesを汎用コンテナ(たとえばTInterfaceList)に入れ始めると、問題が発生します。IInterfacesしか保持できないため、キャストを行う必要があります。
Procedure Test2;
Var
A : IDerived;
B : IBase;
List : TInterfaceList;
Begin
A := TImplementation.Create As IDerived;
B := IBase(A);
List := TInterfaceList.Create;
List.Add(IInterface(B));
Assert (Supports (List[0], IBase)); //This assertion fails
IBase(List[0]).DoWhatever; //Assuming I declared DoWhatever in IBase, this works fine, but it is not type-safe
List.Free;
End;
不一致の型をキャッチするために、ある種のアサーションが必要です。この種のことは、Is演算子を使用してオブジェクトで実行できますが、インターフェイスでは機能しません。さまざまな理由から、サポートされているインターフェイスのリストにIBaseを明示的に追加したくありません。ハードキャストIBase(List [0])が安全な方法である場合に、真と評価されるような方法でTImplementationとアサーションを記述できる方法はありますか?
編集:
答えの1つに出てきたので、TImplementationが実装するインターフェイスのリストにIBaseを追加したくない2つの主な理由を追加します。
第一に、それは実際には問題を解決しません。Test2で、式が次の場合:
Supports (List[0], IBase)
trueを返します。これは、ハードキャストを実行しても安全であることを意味するものではありません。QueryInterfaceは、要求されたインターフェースを満たすために別のポインターを返す場合があります。たとえば、TImplementationがIBaseとIDerived(およびIInterface)の両方を明示的に実装している場合、アサーションは正常に渡されます。
Assert (Supports (List[0], IBase)); //Passes, List[0] does implement IBase
しかし、誰かが誤ってアイテムをIInterfaceとしてリストに追加したと想像してください
List.Add(Item As IInterface);
アサーションは引き続き通過します-アイテムは引き続きIBaseを実装しますが、リストに追加された参照はIInterfaceのみです-IBaseにハードキャストしても意味のあるものは何も生成されないため、次のハード-キャストは安全です。動作が保証されている唯一の方法は、キャストまたはサポートを使用することです。
(List[0] As IBase).DoWhatever;
ただし、これは、リストに項目を追加してIBaseタイプであることを確認するコードの責任であるため、イライラするパフォーマンスコストです。これを想定できるはずです(したがって、この想定がfalse)。誰かがいくつかのタイプを変更した場合に後で間違いを見つけることを除いて、アサーションは必要さえありません。この問題の元のコードもかなりパフォーマンスが重要であるため、ほとんど達成されないパフォーマンスコスト(実行時に不一致の型をキャッチするだけですが、より高速なリリースビルドをコンパイルする可能性はありません)は避けたいものです。
2番目の理由は、参照が等しいかどうかを比較できるようにしたいのですが、同じ実装オブジェクトが異なるVMTオフセットを持つ異なる参照によって保持されている場合、これは実行できません。
編集2:例を使用して上記の編集を拡張しました。
編集3:注:問題は、ハードキャストを回避する方法ではなく、アサーションが通過した場合にハードキャストが安全になるようにアサーションをどのように定式化できるかです。ハードキャストステップを別の方法で実行する方法、または完全に回避する方法はありますが、実行時のパフォーマンスコストがかかる場合、それらを使用することはできません。後でコンパイルできるように、アサーション内のチェックのすべてのコストが必要です。
そうは言っても、誰かがパフォーマンスコストやタイプチェックの危険性なしに問題を完全に回避できれば、それは素晴らしいことです!