如何解决错误 "E2010 Incompatible types: 'TGUID' and 'T'"?

How do I resolve the error "E2010 Incompatible types: 'TGUID' and 'T'"?

这对我来说有点令人费解,因为我正在处理一个有几十个接口的单元,这些接口都基于这个基本接口定义:

type
  IDataObject = interface(IInterface)
    ['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
    function This: TDataObject;
  end;
  ISomeObject = interface(IDataObject)
    ['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
    <Blah>
  end;

这意味着他们都有一个方法'This',return是接口后面的class,有时需要放入列表视图和东西,但是对于这个问题它并不重要,因为我想要一个具有附加功能的通用 class,可以应用于任何派生接口。 (并且任何派生接口都有自己的 GUID。)这是通用的 class:

type
  Cast<T: IDataObject> = class(TDataObject)
    class function Has(Data: IDataObject): Boolean;
    class function Get(Data: IDataObject): T;
  end;

看起来并不太复杂,使用class方法是因为Delphi不支持全局泛型函数,除非它们在class中。所以在我的代码中我想使用 Cast<ISomeObject>.Has(SomeObject) 来检查对象是否支持特定的接口。如果可能,Get() 函数只是将对象 return 作为特定类型。那么,接下来的实现:

class function Cast<T>.Get(Data: IDataObject): T;
begin
  if (Data.QueryInterface(T, Result) <> S_OK) then
    Result := nil;
end;

class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
  Result := (Data.QueryInterface(T, Result) = S_OK);
end;

这就是让人恼火的地方!在我的代码的其他地方,我使用 if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ... 并且它工作得很好。在这些通用方法中,ISomeObjectT 取代,应该可以正常工作。但它拒绝编译并给出此错误:

[dcc64 Error] DataInterfaces.pas(684): E2010 Incompatible types: 'TGUID' and 'T'

这很烦人。我需要解决这个问题,但如果不深入研究系统单元的接口代码就找不到合适的解决方案。 (这是唯一允许我在此代码中使用的单位,因为它需要 运行 在许多不同的平台上!)
该错误是正确的,因为 QueryInterface 需要 TGUID 作为参数,但它似乎是从 ISomeObject 获得的。那为什么不来自 T 呢?
我想我正在尝试在这里做不可能的事情...


更具体一点:Source.QueryInterface(ISomeObject, SomeObject) 在不使用任何其他单元的情况下工作正常。所以我希望它能与通用类型一起工作,如果该类型仅限于接口的话。但事实并非如此,我想知道为什么它在接受 ISomeObject 时不接受 T。
您能解释一下为什么它会因泛型类型而不是常规接口类型而失败吗?

QueryInterface()TGUID 作为输入,但接口类型不是 TGUID。编译器在将具有已声明 guid 的接口类型分配给 TGUID 变量时有特殊处理,但这似乎不适用于使用接口约束的通用参数内部。因此,要执行您正在尝试的操作,您只需在运行时读取接口的 RTTI 以提取其实际的 TGUID(参见 Is it possible to get the value of a GUID on an interface using RTTI?),例如:

uses
  ..., TypInfo;

class function Cast<T>.Get(Data: IDataObject): T;
var
  IntfIID: TGUID;
begin
  IntfIID := GetTypeData(TypeInfo(T))^.GUID;
  if (Data.QueryInterface(IntfIID, Result) <> S_OK) then
    Result := nil;
end;

class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
  Cast<T>.Get(Data) <> nil;
end;

话虽如此,您为什么要复制 RTL 已经为您本地提供的功能?

你的整个Cast class是不必要的,只需使用SysUtils.Supports()代替(SysUtils单元是跨平台的),例如:

uses
  ..., SysUtils;

//if Cast<ISomeObject>.Has(SomeObject) then
if Supports(SomeObject, ISomeObject) then
begin
  ...
end;

...

var
  Intf: ISomeObject;

//Intf := Cast<ISomeObject>.Get(SomeObject);
if Supports(SomeObject, ISomeObject, Intf) then
begin
  ...
end;

此外,您的 IDataObject.This 属性 完全没有必要,因为您可以 直接 IDataObject 接口转换为它的 TDataObject 实现对象(Delphi 自 D2010 起支持此类转换),例如:

var
  Intf: IDataObject;
  Obj: TDataObject;

Intf := ...;
Obj := TDataObject(Intf);