将泛型 TArray 转换为 varArray

Posted

技术标签:

【中文标题】将泛型 TArray 转换为 varArray【英文标题】:Converting a generic TArray into a varArray 【发布时间】:2020-05-22 09:06:06 【问题描述】:

我正在尝试将 array of T 转换为 Variant (varArray)。

对于非泛型类型(即:Integer),我使用以下函数:

function ToVarArray(AValues : array of Integer) : Variant;
var
  i : integer;
begin
  Result := VarArrayCreate(
    [Low(AValues), High(AValues)],
    varInteger
  );

  for i := Low(AValues) to High(AValues) do 
    Result[i] := AValues[i];
end;

我在尝试使用通用 TArray 做同样的事情时遇到了一些问题:

uses
  System.Generics.Collections;

type
  TArray = class(System.Generics.Collections.TArray)
  public
    class function  ToVarArray<T>(const AValues: array of T) : Variant; static;
  end;

我尝试了以下方法:

class function  TArray.ToVarArray<T>(const AValues: array of T) : Variant;
var
  i : integer;
  Tmp : T;
begin
  Result := Tmp;
  Result := VarArrayCreate(
    [Low(AValues), High(AValues)],
    VarType(Result)
  );

  for i := Low(AValues) to High(AValues) do 
    Result[i] := AValues[i];
end;

但它在我将T 分配给Variant 的每一行都会产生以下编译错误:

[dcc32 错误] Unit1.pas(36): E2010 不兼容的类型:'Variant' 和 'T'

【问题讨论】:

【参考方案1】:

问题的标题说您要处理通用TArray&lt;T&gt;,但第一句话说它是array of T。您可能会认为这两个术语指的是同一个数据结构(动态数组),而且大多数情况下它们都是如此,但是如果您使用它们来代替过程/函数参数声明,它们就会有所不同。

因此以下方法具有不同的签名并接受不同类型的参数:

class function TArray.ToVarArray<T>(const AValues: TArray<T>): Variant;
class function TArray.ToVarArray<T>(const AValues: array of T): Variant;

虽然第一个不变量接受真正的动态数组作为参数,但后者接受open array。这种不幸的语言设计对于 Delphi 开发人员来说是常规的source of confusion。

Delphi RTL 已经包含将动态数组转换为适当类型的变量数组的函数 - DynArrayToVariant。它将指向动态数组的初始元素的指针和数组的类型信息作为其参数。要使用泛型,您可以编写:

uses
  System.Variants;

class function TArray.DynArrayToVarArray<T>(const AValues: TArray<T>): Variant;
begin
  DynArrayToVariant(Result, @AValues[0], TypeInfo(TArray<T>));
end;

如果您尝试将此例程与静态数组一起使用,您的代码将无法编译:

var
  SA: array[0..2] of Integer;
begin
  SA[0] := 0;
  SA[1] := 1;
  SA[2] := 2;
   E2010 Incompatible types: 'System.TArray<System.Integer>' and 'array[0..2] of Integer' 
  TArray.DynArrayToVarArray<Integer>(SA);
end.

开放数组解决了这个问题,但您需要将其“转换”为动态数组才能与 RTL 的DynArrayToVariant 一起使用。

class function TArray.OpenArrayToVarArray<T>(const AValues: array of T): Variant;
var
  LArray: TArray<T>;
  Index: Integer;
begin
  SetLength(LArray, Length(AValues));
  for Index := Low(AValues) to High(AValues) do
    LArray[Index] := AValues[index];
  Result := DynArrayToVarArray<T>(LArray);
end;

var
  DA: TArray<Integer>;
  SA: array[0..2] of Integer;
begin
  DA := [0, 1, 2];
  SA[0] := 0;
  SA[1] := 1;
  SA[2] := 2;
   these all work 
  TArray.OpenArrayToVarArray<Integer>(DA); // dynamic array
  TArray.OpenArrayToVarArray<Integer>(SA); // static array
  TArray.OpenArrayToVarArray<Integer>([0, 1, 2]); // open array constructor
end.

上述OpenArrayToVarArray&lt;T&gt; 例程在性能和内存使用方面都非常低效,因为它将元素从开放数组一一复制到动态数组。如果你真的需要支持开放数组,你可能应该编写更好的实现,灵感来自 RTL 的DynArrayToVariant,但是我发现那个也不是最理想的。

【讨论】:

以上是关于将泛型 TArray 转换为 varArray的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能将泛型类型的一个实例转换为另一个实例?

将泛型列表List<T;转换为指定属性的XML

当两个类型参数在C#中实现公共接口时,如何将泛型强制转换为它实现的接口

是否可以将泛型重载限制为属性类型?

如何将泛型类型参数限制为 System.Enum [重复]

将泛型限制为可以为空的事物