将TInterfacedObject转换为接口

5

根据Delphi文档,我可以使用as运算符将TInterfacedObject转换为接口。

但是它对我不起作用。转换会导致编译错误:"运算符不适用于此操作数类型"。

我正在使用Delphi 2007。

这里是一些代码(控制台应用程序)。标有错误的行已经标记。

program Project6;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  IMyInterface = interface
    procedure Foo;
  end;

  TMyInterfacedObject = class(TInterfacedObject, IMyInterface)
  public
    procedure Foo;
  end;

procedure TMyInterfacedObject.Foo;
begin
  ;
end;

var
  o: TInterfacedObject;
  i: IMyInterface;
begin
  try
    o := TMyInterfacedObject.Create;
    i := o as IMyInterface;  // <--- [DCC Error] Project6.dpr(30): E2015 Operator not applicable to this operand type
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

文档库是否正确?链接页面中的第一句话是:“实现接口的类可以在接口上使用 as 运算符进行动态绑定。”。 - mjn
3个回答

14

快速回答

你的接口需要有一个GUID才能让as运算符正常工作。在IMyInterface = interface之后的第一行,方法定义之前,按下Ctrl+G生成一个新的GUID。

详细解释

as运算符用于接口需要一个GUID,因为它会调用IUnknown.QueryInterface,而后者需要一个GUID。如果你在将INTERFACE转换为其他类型的INTERFACE时遇到这个问题,那就没问题了。

但是,你首先不应该将TInterfacedObject转换为接口,因为那意味着你持有实现对象(TInterfacedObject)和实现接口(IMyInterface)的引用。这是有问题的,因为你混合了两种生命周期管理概念:TObject存在直到有人在它们上面调用.Free;你可以合理地确保没有人在你不知情的情况下调用.Free。但是接口是具有引用计数的:当你将接口分配给变量时,引用计数会增加;当该实例超出范围(或被分配给其他值)时,引用计数会减少。当引用计数变为0时,对象将被释放(.Free)!

以下是一些看似无害的代码,但会很快陷入麻烦:

procedure DoSomething(If: IMyInterface);
begin
end;

procedure Test;
var O: TMyObjectImplementingTheInterface;
begin
  O := TMyObjectImplementingTheInterface.Create;
  DoSomething(O); // Works, becuase TMyObject[...] is implementing the given interface
  O.Free; // Will likely AV because O has been disposed of when returning from `DoSomething`!
end;

修复方法非常简单:将 O 的类型从 TMyObject[...] 更改为 IMyInterface,就像这样:

procedure DoSomething(If: IMyInterface);
begin
end;

procedure Test;
var O: IMyInterface;
begin
  O := TMyObjectImplementingTheInterface.Create;
  DoSomething(O); // Works, becuase TMyObject[...] is implementing the given interface
end; // `O` gets freed here, no need to do it manually, because `O` runs out of scope, decreases the ref count and hits zero.

@David,感谢您修复我的拼写错误。对于这些错误本身,我很抱歉。我经常依赖Opera的拼写检查器,但仍会有一些错误通过它:(特别是对于那些(对我来说)是同音异义词的单词,因为Opera不会将它们标记为拼写错误,而我也没有注意到。再次感谢您。 - Cosmin Prund

2

如果你想使用As或Supports运算符,你需要向接口添加GUID,例如:

type   
  IMyInterface = interface
    ['{00000115-0000-0000-C000-000000000049}']
    procedure Foo;   
  end; 

请参阅 docwiki 了解对象接口和GUID的相关信息。

哦,是的。复制粘贴代码示例的乐趣!谷歌一下你提供的 GUID,显然很多人认为只需复制粘贴代码就是个好主意! - Cosmin Prund
是的,它是复制的,是从我链接到的docwiki复制的。我不认为这有什么问题,因为OP应该生成一个唯一的GUID... - Remko
2
实际上,@Cosmin,从我得到的谷歌搜索结果来看,似乎每个人都复制了整个Delphi语言指南,而不仅仅是那个GUID。 - Rob Kennedy

0
如果您将对象o定义为正确的类型,转换将自动进行。否则,您可以始终使用supports()和/或自己调用QueryInterface
var
  o: TMyInterfacedObject;
  i: IMyInterface;
begin
  try
    o := TMyInterfacedObject.Create;
    i := o;
  except
    on E:Exception do
      Writeln(E.Classname, ': ', E.Message);
  end;
end.

Supports()QueryInterface()都需要一个GUID。如果as运算符不起作用,那么这两个变体也不会起作用。 - Cosmin Prund
QCosmin:是的,你说得对。我总是用GUID定义我的接口,所以没有注意到... - Ritsaert Hornstra
你仍然持有这两个引用,阅读 @David 在同一问题上的答案。 - jachguate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接