TDictionary 转换在引用包中失败

TDictionary cast fails in referenced package

TDictionary 已分配给 TObject 变量。我可以在同一项目中将 TOBject 转换回 TDictionary。但是,如果在引用的 bpl 项目中完成转换,则转换会失败,具体取决于引用项目的方式。

测试代码:

procedure TestTDefault.Test1;
var
  Obj: TObject;
begin
  Obj := TDictionary<string, Integer>.Create;
  Check(Obj is TDictionary<string, Integer>);
  CheckNotNull(Obj as TDictionary<string, Integer>);

  // this calls the function in the referenced bpl package.
  // Returns True if Obj can be cast back to a TDictionary.
  CheckTrue(TestIfDictionary(Obj)); // outcome depends if referenced packge is included in <DCC_UsePackage>
end;

依赖包中的函数:

function TestIfDictionary(aObject: TObject): Boolean;
begin
  Result := aObject is TDictionary<string,Integer>;
end;

我有一个只有两个包的简单项目组:

  1. DUnit 测试运行器(Package1Tests.exe)
  2. Package1.bpl

两个包具有相同的 compiler/linker 选项集。

非常奇怪的是,只有当 Package1 未被列为运行时包时,测试才有效:

但是,如果 Package1 被列为运行时包,测试将失败!!

我正在使用 XE2。至于目的,这个问题是在处理RTTI的时候浮出水面的。我能够在这个简单的测试项目中隔离问题。问题与 RTTI 无关。 请注意,如果我不使用 TDictionary 而使用 TStringList,那么测试总是有效。所以问题似乎与泛型有某种关系。 如果需要,我可以提供简单的测试项目。

我花了很多时间来追踪这个问题。我很高兴我发现了引发问题的原因。但不幸的是我不明白为什么会这样。

您应该为此泛型使用类型声明 class。

type
 Tx = TDictionary<string, Integer>;

.....

Obj := Tx.Create;
Check(Obj is Tx);

RTTI 在您的情况下无关紧要。

您在这里遇到的问题与泛型实例化有关。每个模块都单独实例化泛型类型,因此它们具有不同的类型标识。这是泛型设计及其与运行时包交互的基本限制。

您可以在包中的一个单元中实例化通用类型,例如:

type
  TDictionaryStringInteger = class(TDictionary<string, Integer>);

然后用TDictionaryStringInteger代替TDictionary<string, Integer>。但是,这会严重削弱您的代码,因为它会阻止您编写使用泛型类型的泛型方法。

摆脱束缚的另一种方法是停止使用运行时包。坦率地说,这对我来说似乎更有吸引力。