有没有办法在Delphi中动态转换泛型集合的项类型?

时间:2015-03-09 04:46:52

标签: delphi generics dynamic-cast

与普通对象的情况不同,不可能在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,但在某些情况下,它确实都有用并且&#34;正确&#34;做这样的事情。

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

因此,load-object-from-stream代码必须是完全通用的,因此,它需要动态地转换现有的TList<TObject>变量( 明确定义的变量)代码)到TList<MyTObjectSubClass>的确切类型(我当时通过使用RTTI已经了解到),以便能够将从流加载的此对象传递到其最终目标变量。

再说一遍,是否有任何方法可以实现这一点,或者相反实际上完全不可能使用通用代码(即没有明确具有某种类型的代码)分配给非预先知道的泛型集合&#34;如果[xxx的类型是TList<TMyObject1>]那么......如果[xxx的类型是TList<TMyObject2>]那么......其他......&#34;测试,包含显式提到它应该支持的每种泛型类型?)

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

修改

根据@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);

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

有关(必要)&#34; hacky detection&#34;的更多信息在代码中提到,请参阅this question。完成后,我将知道泛型集合及其子项的确切类型,例如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>部分。如何实施?

注:我的问题是的以理解如何序列化/反序列化可枚举的内容通过一个流(其当然可以进行通过简单地迭代项在枚举然后迭代的为每个节点流保存/加载代码,其中首先在流中给出项目数量)。问题在于如何创建通用代码,这些代码能够重新创建/填充任何类型的TObject下行的泛型集合,这些集合的类型只能在运行时获知,然后最终将其输入到最初枚举的对象字段中通过RTTI在流加载代码的开头。例如,假设已处理的字段具有类型TList<TSomeTObjectDescendant>,并且您可以使用function load_list_TSomeTObjectDescendant_subitems(input_stream : TStream) : array of TSomeTObjectDescendant之类的调用轻松地从流中加载其子对象。那么我怎样才能将这些子项目放入TList<TSomeTObjectDescendant>字段?

1 个答案:

答案 0 :(得分:0)

在编译时解析类型转换和变量声明(尽管isas强制转换是在运行时根据编译器提供的RTTI执行的。必须为编译器知道要转换的类型以及分配给的变量的类型。所以你要求的东西根本不可能与泛型。无论如何,不​​是你描述它的方式。