有什么方法可以在 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>
字段?
类型转换和变量声明在编译时进行解析(尽管 is
和 as
转换是在运行时根据编译器提供的 RTTI 执行的)。编译器必须知道要强制转换的类型和要分配给的变量的类型。因此,泛型根本不可能满足您的要求。反正不是你描述的那样。
不同于普通对象的情况,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>
字段?
类型转换和变量声明在编译时进行解析(尽管 is
和 as
转换是在运行时根据编译器提供的 RTTI 执行的)。编译器必须知道要强制转换的类型和要分配给的变量的类型。因此,泛型根本不可能满足您的要求。反正不是你描述的那样。