Monday, September 24, 2007

DN4DP#18: .NET only: New array syntax

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book.

Last time we looked at multi-unit namespace support. Now we will jump to a new array syntax supported in .NET.

Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"New array syntax

While native Delphi supports both static and dynamic arrays, Delphi for .NET now also supports multi-dimensional, rectangular dynamic arrays. These differ from jagged array of arrays in that there is only a single, continuous block of memory allocated for the items in it, and the size of all dimensions can be set dynamically at runtime. This is mostly a performance and memory usage optimization, but it is also required to be able to interface with external code that uses them.

The syntax to declare a multi-dimensional dynamic array is array[,] with one comma for each extra dimension. To allocate a new array, use the New(array [dim1, dim2 ..] of TElement) syntax. To change the size of an existing array, use SetLength with one or more dimension parameters – this will preserve the contents of the array.

var
MyArray: array of integer;
JaggedArray: array of array of integer;
MyMatrix: array[,] of integer;
MyCube: array[,,] of integer;
begin
MyArray := New(array [4] of integer);
JaggedArray := New(array [3] of array of integer);
MyMatrix := New(array [3,3] of integer);
MyCube := New(array [2,2,2] of integer);
//...
SetLength(MyMatrix, 10, 20);
SetLength(MyCube, 10, 20, 30);
end;


Note: While it is possible to create new arrays using SetLength, the New syntax generates slightly smaller and more efficient code. And SetLength cannot currently (Delphi 2006) be used to create a new multi-dimensional [,] array.


There are two new ways to create a new initialized dynamic array from a list of elements. You can use the New statement and follow the array type with a parantesed list of elements. Or you can use a new TArrayType.Create constructor syntax with the elements as parameters – this syntax is also supported in Win32.

begin 
MyArray := New(array[] of integer, (1, 2, 3));
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
// ...
MyArray := TIntegerArray.Create(1, 2, 3);
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
end;

This way of initializing dynamic arrays inline is a great improvement of the old way of first allocating the array using SetLength and then explicitly setting the value of each indexed element. This is particularly useful when calling one of the many FCL methods that have array parameters.

unit NewArraySyntaxU;

interface

procedure Test;

implementation

type
TStaticIntegerArray = array[0..5] of integer;
TIntegerArray = array of integer;
TJaggedArray = array of TIntegerArray;
// New in D2005, .NET only
{$IFDEF CLR}
TIntegerMatrix = array[,] of integer;
TIntegerCube= array[,,] of integer;
{$ENDIF}

{$IFDEF CLR}
procedure InnerDumpArray(const Prefix: string; A: System.Array);
var
O: TObject;
begin
write(Prefix);
for O in A do
begin
if O is System.Array
then InnerDumpArray(#13#10' ', System.Array(O))
else Write(O, ', ');
end;
end;

procedure DumpArray(const Name: string; A: System.Array);
begin
write(Name, ' (Rank=', A.Rank, ')' );
InnerDumpArray(': ', A);
Writeln;
end;
{$ELSE}
procedure DumpArray(const Name: string; const A: array of integer); overload;
var
I: Integer;
begin
write(Name, ': ');
for I in A do
Write(I, ', ');
Writeln;
end;

procedure DumpArray(const Name: string; const A: TJaggedArray); overload;
var
I: Integer;
IA : TIntegerArray;
begin
writeln(Name, ': ');
for IA in A do
begin
write(' ');
for I in IA do
Write(I, ', ');
Writeln;
end;
end;
{$ENDIF}

procedure Test;
var
MyStatics: TStaticIntegerArray;
MyArray: TIntegerArray;
JaggedArray: TJaggedArray;
I: integer;
{$IFDEF CLR}
MyMatrix: TIntegerMatrix;
MyCube: TIntegerCube;
{$ENDIF}
begin
// In .NET static arrays are implicitly allocated by the compiler at runtime
// and all elements are initially cleared (0)
// In Win32, static arrays are stored on the stack and contain random values
MyStatics[1] := 42;
DumpArray('MyStatics', MyStatics);

{$IFDEF CLR}
// Use New syntax to create a new array
MyArray := New(array [4] of integer);
MyArray[0] := 13;
DumpArray('MyArray1', MyArray);

// To New up a truly jagged array, new up each dimension separately
JaggedArray := New(array [3] of array of integer);
for I := Low(JaggedArray) to High(JaggedArray) do
JaggedArray[I] := New(array [I+1] of integer);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray1', JaggedArray);

// Use New(Type, dim1, dim2) to create a rectangular jagged array (array of arrays)
JaggedArray := New(TJaggedArray, 3, 2);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 1] := 3;
DumpArray('JaggedArray2', JaggedArray);

// New also supports initializing the array elements
MyArray := New(array[] of integer, (1, 2, 3));
DumpArray('MyArray2', MyArray);
JaggedArray := New(array[] of array[] of integer,
(New(array[] of integer, (1, 2, 3)),
New(array[] of integer, (1, 2)),
New(array[] of integer, (1))));
DumpArray('JaggedArray3', JaggedArray);
{$ENDIF}

// Use SetLength to change the size of an existing array
SetLength(MyArray, 1);
DumpArray('MyArray3', MyArray);

// You can allocate a rectangualar "jagged" array using a single SetLength call
SetLength(JaggedArray, 3, 3);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray4', JaggedArray);

// To allocate a truly jagged array, use SetLength inside a loop
SetLength(JaggedArray, 3);
for I := 0 to 2 do
SetLength(JaggedArray[I], I+1);
JaggedArray[0, 0] := 1;
JaggedArray[1, 1] := 2;
JaggedArray[2, 2] := 3;
DumpArray('JaggedArray5', JaggedArray);

// Finally you can use the TArray.Create syntax to initialize an array with elements
MyArray := TIntegerArray.Create(1, 2, 3); // New in Win32 since D7
DumpArray('MyArray4', MyArray);
{$IFDEF CLR}
JaggedArray := TJaggedArray.Create(
TIntegerArray.Create(1, 2, 3),
TIntegerArray.Create(1, 3),
TIntegerArray.Create(1));
DumpArray('JaggedArray6', JaggedArray);

// You can also New up rectangular multidim [,] arrays
MyMatrix := New(array [3,3] of integer);
MyMatrix[2,2] := 19;
DumpArray('MyMatrix1', MyMatrix);

MyCube := New(array [2,2,2] of integer);
MyCube[1,0,1] := 9;
DumpArray('MyCube1', MyCube);

// Note: for [,] arrays you cannot used the type alias in the New statement
// MyMatrix := New(TIntegerMatrix, ((1,2,3), (4,5,6)));
MyMatrix := New(array[,] of integer, ((1,2,3), (4,5,6)));
DumpArray('MyMatrix2', MyMatrix);
MyCube := New(array[,,] of integer, (((1,2), (5,6)), ((3,4), (7,8))));
DumpArray('MyCube2', MyCube);

// Note that SetLength does not work correctly for *uninitialized* [,] arrays in 2006
// SetLength does work for initialized [,] arrays
SetLength(MyMatrix, 1, 2);
DumpArray('MyMatrix3', MyMatrix);
SetLength(MyCube, 1, 2, 3);
DumpArray('MyCube3', MyCube);
{$ENDIF}

end;

end.

"

1 comment:

Kells said...

Did you know that every element of an array is associated with a unique index number?



Copyright © 2004-2007 by Hallvard Vassbotn