有什么方法可以在 Delphi 中动态转换泛型集合的项目类型吗?

Is there any way to dynamically cast the item type of a generics collection in Delphi?

不同于普通对象的情况,Delphi中不能直接给不同相关类型的泛型赋值,如下:

可能(普通物体):

var
   var_1 : TObject;
   var_2 : MyTObjectSubClass;

var_1 := var_2; //Works

不可能(泛型):

var
   var_1 : TList<TObject>;
   var_2 : TList<MyTObjectSubClass>;

var_1 := var_2; //Does not compile

不过 可以使用强制转换来完成此操作,如下所示:

var
   var_1 : TList<TObject>;
   var_2 : TList<MyTObjectSubClass>;

var_1 := TList<TObject>(var_2); //Works

这就需要能够以某种方式动态地转换泛型(即动态地参数化它们的泛型类型规范),但我还没有找到一种方法来做这个,所以我的问题是:这有可能吗?

我确实知道与此相关的covariance/contravariance problems,但在某些情况下,这样做确实既有用又"correct"。

这种情况的一个例子是我正在为 TStream 上 Delphi 对象的通用流式传输编写的当前代码,其中接收端知道对象的确切类型通过流传入,例如TList<MyTObjectSubClass>。这种类型信息是通过 RTTI 提取的(从提供的目标变量中提取加载对象),所以我 不能 在我的流加载中明确提及确切的泛型类型提前编码,而是必须通过 RTTI(即 )检测它,然后将其写入目标变量,我只有在那个 运行 时间点才能知道确切的类型.

因此,从流中加载对象的代码必须是完全通用的,因此,它需要动态转换现有的 TList<TObject> 变量( 在代码中明确定义)到 TList<MyTObjectSubClass> 的确切类型(我当时刚刚通过使用 RTTI 了解了这一点),以便能够将从流加载的这个对象传递给它的最终目标变量。

再一次,有没有什么办法可以做到这一点,或者相反,实际上完全不可能使用通用代码(即没有明确具有某种 "if [type of xxx is TList<TMyObject1>] then ... else if [type of xxx is TList<TMyObject2>] then ... else ..." 测试,包含明确提及它应该支持的每个泛型类型)?

PS。 流加载对象的泛型类型显然已经存在于程序中的某个地方(因为它是通过 RTTI 对流加载对象应该写入的目标变量得出结论的),所以我不是询问完整的 运行 时间动态创建泛型类型 ,而是询问如何能够动态地 从那些已经在编译时定义的泛型类型中选择正确的一种time 在程序中,然后将变量转换为该类型。

编辑:

应@RemyLebeau 的请求,这里有一些来自我的应用程序的更多示例代码,来自它的流加载功能:

var
   source_stream    : TStream;
   field_to_process : TRttiField;
   field_type       : TRttiType;
   loaded_value     : TValue;
   temp_int         : integer;

//...
//The fields of any object given to the streaming function are
//enumerated and sorted here
//...

//Then, for each field (provided in field_to_process),
//the following is done:

case field_to_process.FieldType.TypeKind of

   //...

   tkInteger:
   begin
      source_stream.ReadBufferData(temp_int);
      loaded_value := TValue.From(temp_int);
   end;

   tkString,
   tkLString,
   tkWString,
   tkUString:
   begin
      source_stream.ReadBufferData(noof_raw_bytes_in_string_data);
      SetLength(raw_byte_buf, noof_raw_bytes_in_string_data + 4);
      source_stream.ReadBuffer(raw_byte_buf[0], noof_raw_bytes_in_string_data);
      temp_str := used_string_encoding.GetString(raw_byte_buf, 0, noof_raw_bytes_in_string_data);
      loaded_value := TValue.From(temp_str);
   end;

   tkClass:
   begin
      is_generics_collection_containing_TObject_descendants := <does some hacky detection here>; //Thanks Remy :-)

      if is_generics_collection_containing_TObject_descendants then
      begin

         <magic code goes here that loads data from the stream into the currently processed field, whose type has been detected to be of some specific generics collection type>

      end;
   end;

   //...

end;

field_to_process.SetValue(self, loaded_value);

这应该可以更好地概述我的问题。字符串和整数的多余代码仅用于上下文,通过显示如何处理一些简单类型。

有关代码中提到的(必然)"hacky detection"的更多信息,请参阅。这样做之后,我将知道泛型集合及其子项的确切类型,例如 TList<TSomeTObjectDescendant>.

所以,正如您现在希望看到的那样,问题是关于 <magic code goes here that loads data from the stream into the currently processed field, whose type has been detected to be of some specific generics collection type> 部分的。如何实施?

注意:我的问题是 不是 了解如何通过流 serialize/deserialize 可枚举的内容(当然可以通过简单地迭代项目来完成在可枚举中,然后为它们中的每一个递归流 saving/loading 代码,其中项目的数量首先在流中给出)。问题是如何创建能够 recreate/populate 任何类型的 TObject 后代的泛型集合的泛型代码,其类型你只能在 运行 时才知道,然后最终将其放入RTTI 最初在流加载代码开始时枚举的对象字段。例如,假设已处理字段的类型为 TList<TSomeTObjectDescendant>,并且您可以使用 function load_list_TSomeTObjectDescendant_subitems(input_stream : TStream) : array of TSomeTObjectDescendant 之类的调用轻松地从流中加载其子对象。我怎样才能将这些子项放入 TList<TSomeTObjectDescendant> 字段?

类型转换和变量声明在编译时进行解析(尽管 isas 转换是在运行时根据编译器提供的 RTTI 执行的)。编译器必须知道要强制转换的类型和要分配给的变量的类型。因此,泛型根本不可能满足您的要求。反正不是你描述的那样。