Search code examples
delphigenericsdelphi-xe7variant

Converting a generic TArray into a varArray


I'm trying to convert an array of T into a Variant (varArray).

With not-generic types (i.e: Integer), I'm using the following function:

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;

I'm having some problems while trying to do the same thing with a generic 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;

I've tried the following:

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;

But It produces the following compile error at each row where I'm assigning a T to a Variant:

[dcc32 Error] Unit1.pas(36): E2010 Incompatible types: 'Variant' and 'T'


Solution

  • The title of the question says that you'd like to process generic TArray<T>, but the first sentence say it's array of T. You might think that both terms refer to the same data structure (dynamic array), and most of the time they do, but they make difference if you use them in place of a procedure/function argument declaration.

    Therefore the following methods have different signatures and accept different types of parameters:

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

    While the first invariant accepts true dynamic array as a parameter, the latter takes an open array. This unfortunate language design is regular source of confusion for Delphi developers.

    Delphi RTL already contains function that converts dynamic array to variant array of appropriate type - DynArrayToVariant. It takes pointer to initial element of dynamic array and array's type information as its arguments. To make use of generics you would write:

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

    Your code would fail to compile if you try to use this routine with a static array:

    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.
    

    Open array solves this issue, but you need to "convert" it to dynamic array in order to use it with RTL's 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.
    

    The above OpenArrayToVarArray<T> routine is pretty ineffective both in terms of performance and memory usage, because it copies elements one by one from open array to dynamic array. If you really need to support open arrays you should probably write better implementation inspired by RTL's DynArrayToVariant, however I find that one to be sub-optimal too.