Initialising dynamic arrays

When writing some unit tests a while ago I found myself needing to initialise some dynamic arrays with test data. It would be nice if we could do something like this:

var
  A: array of Integer;
begin
  A := (1,2,3,4);  // !! WRONG
end;

but we can't. So I decided to write some functions to initialise dynamic arrays to the contents of another array, be it a constant, a literal or another dynamic array.

The result was a set of overloaded routines, one for each data type I needed to handle, for example for Integer and string arrays I had:

function CloneArray(const A: array of Integer): TIntegerDynArray; overload;
var
  Idx: Integer;
begin
  SetLength(Result, Length(A));
  for Idx := Low(A) to High(A) do
    Result[Idx - Low(A)] := A[Idx];
end;

function CloneArray(const A: array of string): TStringDynArray; overload;
var
  Idx: Integer;
begin
  SetLength(Result, Length(A));
  for Idx := Low(A) to High(A) do
    Result[Idx - Low(A)] := A[Idx];
end;

Noticing how similar these functions were - only the first line differs - I decided to try a generic approach. Since I didn't want to parameterise both the type of the array (e.g. the TIntegerDynArray return value) and the array element (e.g. Integer) I first decided to change the return values of the functions to use the TArray<T> type defined in the System unit. So the function prototypes got changed to

function CloneArray(const A: array of Integer): TArray<Integer>; overload;

and

function CloneArray(const A: array of string): TArray<string>; overload;

The implementation of the routines did not need changing. It seemed clear how to parameterise the routines: just replace the Integer or string types with T, like this:

function CloneArray<T>(const A: array of T): TArray<T>;

Wrong! At this point I learned that you can't parameterise global routines, as my blog entry "Being dim #2: No generic global procedures" explains. I needed to make the CloneArray routine a class method of some suitable class. Rather than create a new class I cast around and decided to extend the TArray class defined in the Generics.Collections unit.

type
  TArrayEx = class(TArray)
  public
    class function CloneArray<T>(const A: array of T): TArray<T>;
  end;

...

class function TArrayEx.CloneArray<T>(const A: array of T): TArray<T>;
var
  Idx: Integer;
begin
  SetLength(Result, Length(A));
  for Idx := Low(A) to High(A) do
    Result[Idx - Low(A)] := A[Idx];
end;

Don't confuse Generics.Collections.TArray with System.TArray<T> - the first is a class, the second a generic dynamic array type.

CloneArray assumes that straighforward assignment of array elements is what we want. For example if the array elements are objects, just the object pointer is copied. If you need the object's fields to be physically copied this simplistic approach won't work.

I'll close this post with an example of use. Here's a code fragment that initialises string and integer arrays:

var
  IA: TArray<Integer>;
  SA: TArray<string>;
begin
  IA := TArrayEx.CloneArray<Integer>([1,2,3,5,7,11]);
  SA := TArrayEx.CloneArray<string>(['bonnie', 'clyde']);
end;

Comments

  1. Anonymous1:56 pm

    WRT your opening question, you can in fact do this:

    uses Types;

    var
    A: TIntegerDynArray;
    begin
    A := TIntegerDynArray.Create(1,2,3,4);
    end;

    There's nothing special to TIntegerDynArray - it's just to use a predefined typedef. So, this works as well:

    type
    TMyIntArray = array of Integer;
    var
    A: TMyIntArray;
    begin
    A := TMyIntArray.Create(1,2,3,4);
    end;

    I think this syntax was added in something like D2005 or D2006.

    ReplyDelete
  2. Thanks Chris.

    I'd not come across this before. Very handy.

    This makes some of this post redundant with one exception. The TArrayEx.CloneArray method works for cloning another dynamic array stored in a variable whereas the Create construct doesn't work in this case.

    ReplyDelete
  3. I've also just noticed that this also works:

    var
      K: TArray<Integer>;
    begin
      K := TArray<Integer>.Create(1, 2, 3);
    end;

    ReplyDelete
  4. Anonymous9:22 pm

    Actually, now I come to think of it, for cloning, there's the single parameter version of the Copy standard function -

    A2 := Copy(A1);

    ReplyDelete
  5. One more new one for me! Thanks.

    ReplyDelete
  6. Wow!!!
    A := TIntegerDynArray.Create(1,2,3,4);
    I can't believe that I have never seen this before...

    ReplyDelete

Post a Comment

Comments are very welcome, but please don't comment here if:

1) You have a query about, or a bug report for, one of my programs or libraries. Most of my posts contain a link to the relevant repository where there will be an issue tracker you can use.

2) You have a query about any 3rd party programs I feature, please address them to the developer(s) - there will be a link in the post.

3) You're one of the tiny, tiny minority who are aggressive or abusive - in the bin you go and reported you will be!

Thanks

Popular posts from this blog

Deleting elements from a dynamic array

New String Property Editor Planned For RAD Studio 12 Yukon 🤞