【问题标题】:Do Generics Mess Up Interface Name Mapping?泛型是否会破坏接口名称映射?
【发布时间】:2009-11-11 23:54:33
【问题描述】:

基本上,我希望一个类能够实现同一个通用接口的两个不同版本。

考虑这段代码

type
  // a generic interface
  ITest<T> = interface
    ['{6901FE04-8FCC-4181-9E92-85B73264B5DA}']
    function Val: T;
  end;

  // a class that purports to implement two different types of that interface
  TTest<T1, T2> = class(TInterfacedObject, ITest<T1>, ITest<T2>)
  protected
    fV1: T1;
    fV2: T2;
  public
    constructor Create(aV1: T1; aV2: T2);
    function Val: T1;               // Val() for ITest<T1>
    function T2Val: T2;             // Val() for ITest<T2>
    function ITest<T2>.Val = T2Val; // mapping
  end;

constructor TTest<T1, T2>.Create(aV1: T1; aV2: T2);
begin
  inherited Create;
  fV1 := aV1;
  fV2 := aV2;
end;

function TTest<T1, T2>.T2Val: T2;
begin
  result := fV2;
end;

function TTest<T1, T2>.Val: T1;
begin
  result := fV1;
end;

/////////////
procedure Test;
var
  t : TTest<integer, string>;
begin
  t := TTest<integer, string>.Create(39, 'Blah');
  ShowMessage((t as ITest<string>).Val);            // this works as expected
  ShowMessage(IntToStr((t as ITest<integer>).Val)); // this gets AV
end;

第一个 ShowMessage 按我的预期显示“Blah”,但第二个会崩溃。它崩溃的原因是因为调用调用 T2Val() 而不是 Val() 正如我所期望的那样。显然,冲突解决映射映射了两种接口类型的方法,而不仅仅是 ITest: T2。

所以,这是我的问题。

这是一个错误吗?我的意思是,Embarcadero 是否打算支持这一点并且只是错误地实施它?还是他们根本就没有打算让程序员做这样的事情? (老实说,我的测试程序竟然编译了,我有点惊讶)

如果这是一个错误,有没有人知道是否有一种解决方法让我让一个类支持两种不同类型的单个泛型接口?

【问题讨论】:

    标签: delphi generics delphi-2010


    【解决方案1】:

    as 具有接口类型使用接口转换,它使用 GUID 来查找接口。对于具有 GUID 的通用接口,每个实例化都获得相同的 GUID。如果单个类型实现了接口的多个副本,那么通过 GUID 查找将导致返回第一个接口。

    如果您不使用接口转换,程序将按预期工作,而是使用如下接口转换:

    procedure Test;
    var
      t : TTest<integer, string>;
    begin
      t := TTest<integer, string>.Create(39, 'Blah');
      ShowMessage(ITest<string>(t).Val);
      ShowMessage(IntToStr(ITest<Integer>(t).Val));
    end;
    

    最初,当为 Win32 实现泛型时,GUID 不允许在泛型接口上使用。然而,通用接口的动态查询对于通用容器场景来说是可取的,并且通常作为一种在算法上下文中查询服务提供者以获取特定类型服务的机制(例如排序或搜索,这需要比较器和平等测试)。因此形成了一个新计划:在通用接口上有一个 GUID,但为通用实例创建类型参数的散列,并将散列折叠(例如异或)到 GUID 中,以便为每个不同且不兼容的实例创建唯一的 GUID。然而,这已经很晚了,在时间限制内不可能有好的实施。但是动态查询的需求仍然存在,所以 GUID 保留了下来。这就是为什么事情会变成今天这样的原因。

    为了解决您的特定情况,我可以推荐的最好方法是使用具有显式 GUID 的不同后代;或者使用不同的机制来查询接口。

    【讨论】:

    • 嗯。很高兴知道。谢谢。你只是巴里信息的金矿。感谢您抽出宝贵时间。
    • 不幸的是,它没有。不过我真的应该调查一下。
    • 不幸的是,在 XE 中仍然没有解决方案。但这会很有帮助!
    【解决方案2】:

    这是一个有趣的问题。似乎正在发生的事情是编译器总是将接口映射到接口的最后一个指定版本(如果交换顺序,那么它会调用另一个方法)。这可能与两个接口具有相同的 GUID 签名这一事实有关,因此调度程序在看到接口调用时对应该调用哪个方法感到困惑。

    这似乎是一个错误,因此应通过质量中心报告。

    【讨论】:

      猜你喜欢
      • 2022-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 1970-01-01
      • 2019-07-13
      相关资源
      最近更新 更多