【问题标题】:How do I resolve the error "E2010 Incompatible types: 'TGUID' and 'T'"?如何解决错误“E2010 不兼容的类型:'TGUID' 和 'T'”?
【发布时间】:2021-03-02 20:27:12
【问题描述】:

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

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

这意味着他们都有一个方法“This”,它返回接口后面的类,有时需要放入列表视图和其他东西,但对于这个问题,这并不重要,因为我想要一个带有额外的泛型类可以应用于任何派生接口的函数。 (并且任何派生接口都有自己的 GUID。)这是泛型类:

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

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

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 错误] DataInterfaces.pas(684): E2010 不兼容的类型: “TGUID”和“T”

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


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

【问题讨论】:

  • 注意:只允许使用系统单元!这是一个严重的限制......
  • 我知道您说您只能使用System 单元,因为您需要跨平台,但您可能是指System 命名空间。在这种情况下,傻瓜中基于 RTTI 的解决方案可能是你能做的最好的。
  • @WimtenBrink 泛型并不像人们期望的那样灵活。很多时候,您最终需要使用黑客来解决其中的限制。我建议您将 report this issue to Embarcadero 作为编译器错误。
  • “问题是这个单元一旦准备好就会由其他开发人员维护。”所以呢。定义规则。让他们清楚。监督这些开发商。 “而使用接口的原因是因为他们经常忘记释放对象,而接口通常会释放自己。”教他们。如果他们学不到这一点,那就让他们走吧。如果他们不能学习这一点,那么他们将不会产生任何具有持久价值的东西。实际上,问题不在于初级开发人员缺乏经验,而在于领导力薄弱。

标签: delphi interface delphi-10.4-sydney


【解决方案1】:

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 类是不必要的,只需使用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);

【讨论】:

  • SysUtils.Supports 的作用与 QueryInterface 完全相同,但由于相同的原因而失败。添加额外的单元会使这个单元更加复杂,我想避免这种情况,因为项目中几乎所有其他单元都会使用它。因此,如果此功能无法在本单元中添加,那么我将删除它,因为 Supports() 可以在其他任何地方使用,如果需要的话。但我希望在这个单元中提供更通用的解决方案。
  • 这用于列表视图和其他附有对象的列表类。而不是要求 (Data as TObject) 我只想使用 Data.This 代替。类定义隐藏在代码中,因此我无法对任何特定类进行类型转换。 (这是故意的!)它的类型是 TDataObject 而不是 TObject 的唯一原因是因为 TObject 没有 IInterface 支持。 (为此需要 TInterfacedObject。)因此,如果需要,可以将 TDataObject.Items 强制转换回特定接口。 (TDataObject.This as ISomeInterface)
猜你喜欢
  • 1970-01-01
  • 2013-06-09
  • 1970-01-01
  • 1970-01-01
  • 2011-10-13
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多