如果我有以下接口和实现它们的类 -
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,因为 IBase 不在 TImplementation 支持的 GUID 列表中。可以通过将声明更改为 -
TImplementation = Class(TInterfacedObject, IDerived, IBase)
然而,即使不这样做,我也已经知道A 实现了 IBase,因为 A 是一个 IDerived,而 IDerived 是一个 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;
但是当我们开始将 IBase 放入一个通用容器(例如 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 显式添加到支持的接口列表中。有什么方法可以编写 TImplementation 和断言,使其评估为真 iff 硬铸造 IBase(List[0]) 是安全的事情吗?
编辑:
正如答案之一中出现的那样,我添加了两个主要原因,我不想将 IBase 添加到 TImplementation 实现的接口列表中。
首先,它实际上并没有解决问题。如果,在 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 类型 - 我们应该能够假设这一点(因此,如果这个假设是,则要捕获的断言错误的)。断言甚至不是必需的,除非有人更改某些类型时捕获以后的错误。这个问题来自的原始代码对性能也相当关键,因此我宁愿避免实现很少的性能成本(它仍然只能在运行时捕获不匹配的类型,但无法编译更快的发布版本) .
第二个原因是我希望能够比较引用是否相等,但如果相同的实现对象由具有不同 VMT 偏移的不同引用持有,则无法做到这一点。
编辑 2:用一个例子扩展了上面的编辑。
编辑3:注意:问题是我如何制定断言,以便在断言通过时硬转换是安全的,而不是如何避免硬转换。有一些方法可以不同地执行硬转换步骤,或者完全避免它,但是如果存在运行时性能成本,我就不能使用它们。我想要在断言中检查的所有成本,以便以后可以编译出来。
话虽如此,如果有人可以在没有性能成本和类型检查危险的情况下完全避免这个问题,那就太好了!