IBase = Interface ['{82F1F81A-A408-448B-A194-DCED9A7E4FF7}'] End; IDerived = Interface(IBase) ['{A0313EBE-C50D-4857-B324-8C0670C8252A}'] End; TImplementation = Class(TInterfacedObject,IDerived) End;
以下代码打印“坏!” –
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;
但是,当我们开始将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添加到支持的接口列表中.有没有办法我可以写TImplementation和断言这样一个方式,它将评估为true如果硬化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:注意:问题是我如何制定断言,以便硬判决是安全的,如果断言通过,而不是如何避免强硬.有一些方法可以不同地进行强硬步骤,或者完全避免这种情况,但是如果运行时性能成本,我就不能使用它们.我想要在断言中检查所有的成本,以便以后可以编译.
话虽如此,如果有人可以完全避免这个问题,没有性能成本,没有类型检查的危险将是巨大的!
解决方法
您所述的目标是能够检查您从列表中获取的内容是否真的是IBase参考.添加IBase作为实现的接口将允许您轻松实现该目标.在这方面,你没有这样做的“两个主要原因”就没有水.
>“我想能够比较引用的平等”:没问题. COM需要如果您在同一对象上使用相同的GUID调用QueryInterface两次,那么您将获得两次相同的接口指针.如果您有两个任意的接口引用,并且将它们两者都转换为IBase,那么当且仅当它们由相同对象支持时,结果将具有相同的指针值.
由于您似乎希望您的列表只包含IBase值,并且您没有Delphi 2009中通用的TInterfaceList< IBase>这将是有帮助的,你可以纪律自己,始终明确地添加IBase值到列表,从来没有任何后裔类型的值.每当你添加一个项目到列表,使用这样的代码:
List.Add(Item as IBase);
这样,列表中的任何重复项都很容易被检测出来,您的“硬盘”就可以放心工作.
>“实际上并不解决问题”:但是,如上所述,
Assert(Supports(List[i],IBase));
当对象明确地实现其所有接口时,可以检查这样的事情.如果您已将项目添加到列表中,可以禁用该断言.启用断言可以检测何时有人更改程序中其他位置的代码,以将错误的项添加到列表中.经常运行您的单元测试也将让您在介绍之后很快就会发现问题.
考虑到以上几点,您可以使用以下代码检查添加到列表中的任何内容是否正确添加:
var AssertionItem: IBase; Assert(Supports(List[i],IBase,AssertionItem) and (AssertionItem = List[i])); // I don't recall whether the compiler accepts comparing an IBase // value (AssertionItem) to an IUnknown value (List[i]). If the // compiler complains,then simply change the declaration to // IUnknown instead; the Supports function won't notice.
如果断言失败,则您添加了一些不支持IBase的列表,或者为某个对象添加的特定接口引用不能作为IBase引用.如果断言通过,那么你知道List [i]会给你一个有效的IBase值.
请注意,添加到列表中的值不需要显式为IBase值.鉴于上面的类型声明,这是安全的:
var A: IDerived; begin A := TImplementation.Create; List.Add(A); end;
这是安全的,因为TImplementation实现的接口形成一个简并为简单列表的继承树.没有分支,其中两个接口不相互继承但具有共同的祖先.如果IBase有两个提示,并且TI实现都实现了这两个,上述代码将无效,因为在A中保存的IBase引用不一定是该对象的“规范”IBase引用.该断言会检测到该问题,您需要将其添加到List.Add(A as IBase)中.
当您禁用断言时,只有在添加到列表中时才会收到正确的类型的费用,而不是从列表中读取.我命名变量AssertionItem以阻止您在该过程的其他地方使用该变量;它只是支持断言,一旦断言被禁用,它就不会有一个有效的值.