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;
WRT your opening question, you can in fact do this:
ReplyDeleteuses 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.
Thanks Chris.
ReplyDeleteI'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.
I've also just noticed that this also works:
ReplyDeletevar
K: TArray<Integer>;
begin
K := TArray<Integer>.Create(1, 2, 3);
end;
Actually, now I come to think of it, for cloning, there's the single parameter version of the Copy standard function -
ReplyDeleteA2 := Copy(A1);
One more new one for me! Thanks.
ReplyDeleteWow!!!
ReplyDeleteA := TIntegerDynArray.Create(1,2,3,4);
I can't believe that I have never seen this before...