Reputation: 745
How do have a variable point to an array of record?
Note: I would like to have pre-defined arrays of TRecord as constants... but along the code, I need variable 'W' to record which array of record to use.
Note that I do not wish to create the array of TRecord in code (dynamically) using Constructor of TRecord, but wish to have static array (as the data will not change).
How do I get variable 'W' to "record" which Array of TRecord?
Please see code below - easier to understand what I mean.
procedure TForm1.Button1Click(Sender: TObject);
type
TTestRec = record
X: string;
Y: Integer;
end;
TMyArr = TArray<TTestRec>;
const
ARRAY_A : TArray<string> = ['A1', 'A2', 'A3', 'A4'];
ARRAY_B : TArray<string> = ['B1', 'B2', 'B3'];
ARRAY_C : array[1..2] of TTestRec = (
(X: 'testC1'; Y:1),
(X: 'testC2'; Y:2)
);
ARRAY_D : array[1..3] of TTestRec = (
(X: 'testD1'; Y:3),
(X: 'testD2'; Y:4)
(X: 'testD3'; Y:9)
);
var
Z : TArray<string>;
W : array of TTestRec;
begin
Z := ARRAY_A; // this works
Z := ARRAY_B; // this works
W := ARRAY_C; // this does not work
W := ARRAY_D; // this does not work
end;
Upvotes: 2
Views: 651
Reputation: 6013
Remy has explained your issues, but I would like to present an alternative answer.
You cannot assign static arrays to dynamic arrays, but you can hide a dynamic array inside a record and make that record perform what you are trying to do.
Each static array is different and so you need to handle each separately, but since you want to use constant arrays I guess the number of different lengths is limited.
Here is a rewrite of your code that handles what I am talking about. The routine Test is very similar in appearance to your example.
unit UnitTest;
interface
implementation
type
TTestRec = record
X: string;
Y: Integer;
end;
TDArray = array of TTestRec;
TSArray2 = array[1..2] of TTestRec;
TSArray3 = array[1..3] of TTestRec;
TMyArr = record
Items : array of TTestRec;
private
function GetItem(const i: integer): TTestRec;
procedure SetItem(const i: integer; const Value: TTestRec);
function GetCount: integer;
public
class operator implicit (a : TDArray ) : TMyArr;
class operator implicit (a : TMyArr ) : TDArray;
class operator implicit (a : TSArray2 ) : TMyArr;
class operator implicit (a : TSArray3 ) : TMyArr;
property Item[ const i : integer ] : TTestRec
read GetItem
write SetItem; default;
property Count : integer
read GetCount;
end;
const
ARRAY_A : TArray<string> = ['A1', 'A2', 'A3', 'A4'];
ARRAY_B : TArray<string> = ['B1', 'B2', 'B3'];
ARRAY_C : TSArray2 = (
(X: 'testC1'; Y:1),
(X: 'testC2'; Y:2)
);
ARRAY_D : TSArray3 = (
(X: 'testD1'; Y:3),
(X: 'testD2'; Y:4),
(X: 'testD3'; Y:9)
);
var
Z : TArray<string>;
W : TMyArr;
procedure Test;
var
iTest : TTestRec;
i : integer;
begin
Z := ARRAY_A; // this works
Z := ARRAY_B; // this works
ARRAY_A[ 1 ] := 'Fred';
W := ARRAY_C; // this does not work
iTest := W[1];
W := ARRAY_D; // this does not work
iTest := W[ 1 ];
// iteration
for i := 0 to W.Count - 1 do
begin
iTest := W[i]; //etc
end;
end;
{ TMyArr }
class operator TMyArr.implicit(a: TMyArr): TDArray;
var
i: Integer;
begin
SetLength( Result, Length( a.Items ) );
for i := 0 to Length( a.Items ) - 1 do
begin
Result[ i ] := a.Items[ i ];
end;
end;
class operator TMyArr.implicit(a: TDArray): TMyArr;
var
i: Integer;
begin
SetLength( Result.Items, Length( a ));
for i := 0 to Length( a ) - 1 do
begin
Result.Items[ i ] := a[i + 1]; //not zero based!
end;
end;
class operator TMyArr.implicit(a: TSArray2): TMyArr;
var
i: Integer;
begin
SetLength( Result.Items, Length(a));
for i := 0 to Length(a) - 1 do
begin
Result.Items[i] := a[i + 1];
end;
end;
function TMyArr.GetCount: integer;
begin
Result := Length( Items );
end;
function TMyArr.GetItem(const i: integer): TTestRec;
begin
Result := Items[ i ];
end;
class operator TMyArr.implicit(a: TSArray3): TMyArr;
var
i: Integer;
begin
SetLength( Result.Items, Length(a));
for i := 0 to Length(a) - 1 do
begin
Result.Items[i] := a[i];
end;
end;
procedure TMyArr.SetItem(const i: integer; const Value: TTestRec);
begin
Items[ i ] := Value;
end;
end.
Have shown how to iterate. I have added a property called Count and shown in the test routine how to iterate
Upvotes: 0
Reputation: 596958
Assigning ARRAY_A
or ARRAY_B
to Z
works, because you are assigning TArray<string>
constants to a TArray<string>
variable. They are all the same type, so they are compatible with each other.
Assigning ARRAY_C
or ARRAY_D
to W
does not work, because you are assigning static array constants to a dynamic array variable. They are different types that are not compatible with each other, as explained in Delphi's documentation:
Type Compatibility and Identity (Delphi)
Type Compatibility
Every type is compatible with itself. Two distinct types are compatible if they satisfy at least one of the following conditions.
- They are both real types.
- They are both integer types.
- One type is a subrange of the other.
- Both types are subranges of the same type.
- Both are set types with compatible base types.
- Both are packed-string types with the same number of characters.
- One is a string type and the other is a string, packed-string, or
Char
type.- One type is
Variant
and the other is an integer, real, string, character, or Boolean type.- Both are class, class-reference, or interface types, and one type is derived from the other.
- One type is
PAnsiChar
orPWideChar
and the other is a zero-based character array of the formarray[0..n] of PAnsiChar
orPWideChar
.- One type is
Pointer
(an untyped pointer) and the other is any pointer type.- Both types are (typed) pointers to the same type and the
{$T+}
compiler directive is in effect.- Both are procedural types with the same result type, the same number of parameters, and type-identity between parameters in corresponding positions.
Assignment Compatibility
Assignment-compatibility is not a symmetric relation. An expression of type T2 can be assigned to a variable of type T1 if the value of the expression falls in the range of T1 and at least one of the following conditions is satisfied:
- T1 and T2 are of the same type, and it is not a file type or structured type that contains a file type at any level.
- T1 and T2 are compatible ordinal types.
- T1 and T2 are both real types.
- T1 is a real type and T2 is an integer type.
- T1 is
PAnsiChar
,PWideChar
,PChar
or any string type and the expression is a string constant.- T1 and T2 are both string types.
- T1 is a string type and T2 is a
Char
or packed-string type.- T1 is a long string and T2 is
PAnsiChar
,PWideChar
orPChar
.- T1 and T2 are compatible packed-string types.
- T1 and T2 are compatible set types.
- T1 and T2 are compatible pointer types.
- T1 and T2 are both class, class-reference, or interface types and T2 is a derived from T1.
- T1 is an interface type and T2 is a class type that implements T1.
- T1 is
PAnsiChar
orPWideChar
and T2 is a zero-based character array of the formarray[0..n] of Char
(when T1 isPAnsiChar
) or ofWideChar
(when T1 isPWideChar
).- T1 and T2 are compatible procedural types. (A function or procedure identifier is treated, in certain assignment statements, as an expression of a procedural type. See "Procedural types in statements and expression" earlier in this chapter.)
- T1 is
Variant
and T2 is an integer, real, string, character, Boolean, interface type orOleVariant
type.- T1 is an
OleVariant
and T2 is an integer, real, string, character, Boolean, interface, orVariant
type.- T1 is an integer, real, string, character, or Boolean type and T2 is
Variant
orOleVariant
.- T1 is the
IUnknown
orIDispatch
interface type and T2 isVariant
orOleVariant
. (The variant's type code must bevarEmpty
,varUnknown
, orvarDispatch
if T1 isIUnknown
, andvarEmpty
orvarDispatch
if T1 isIDispatch
.)
Assigning ARRAY_A
or ARRAY_B
to Z
satisfies the "Assignment Compatibility" requirements. Assigning ARRAY_C
or ARRAY_D
to W
does not.
To solve your problem with the static arrays, you will have to use a pointer instead (a dynamic array is already a pointer), eg:
procedure TForm1.Button1Click(Sender: TObject);
type
TTestRec = record
X: string;
Y: Integer;
end;
PTestRec = ^TTestRec;
const
ARRAY_A : TArray<string> = ['A1', 'A2', 'A3', 'A4'];
ARRAY_B : TArray<string> = ['B1', 'B2', 'B3'];
ARRAY_C : array[1..2] of TTestRec = (
(X: 'testC1'; Y:1),
(X: 'testC2'; Y:2)
);
ARRAY_D : array[1..3] of TTestRec = (
(X: 'testD1'; Y:3),
(X: 'testD2'; Y:4)
(X: 'testD3'; Y:9)
);
var
Z : TArray<string>;
W : PTestRec;
begin
Z := ARRAY_A;
Z := ARRAY_B;
W := @ARRAY_C[1];
W := @ARRAY_D[1];
end;
Note, however, that there is no way to determine from W
itself whether it is pointing at an array[1..2] of TTestRec
or an array[1..3] of TTestRec
, which are two completely different types. So, if you need to use W
to iterate the arrays, you will have to keep track of the acceptable bounds separately, eg:
{$POINTERMATH ON}
procedure TForm1.Button1Click(Sender: TObject);
type
TTestRec = record
X: string;
Y: Integer;
end;
PTestRec = ^TTestRec;
const
ARRAY_A : TArray<string> = ['A1', 'A2', 'A3', 'A4'];
ARRAY_B : TArray<string> = ['B1', 'B2', 'B3'];
ARRAY_C : array[1..2] of TTestRec = (
(X: 'testC1'; Y:1),
(X: 'testC2'; Y:2)
);
ARRAY_D : array[1..3] of TTestRec = (
(X: 'testD1'; Y:3),
(X: 'testD2'; Y:4)
(X: 'testD3'; Y:9)
);
var
Z : TArray<string>;
W : PTestRec;
W_Len, I: Integer;
begin
Z := ARRAY_A;
for I := 0 to High(Z) do begin
// use Z[I] as needed ...
end;
Z := ARRAY_B;
for I := 0 to High(Z) do begin
// use Z[I] as needed ...
end;
W := @ARRAY_C[1];
W_Len := Length(ARRAY_C);
for I := 0 to Pred(W_Len) do begin
// use W[I] as needed ...
end;
W := @ARRAY_D[1];
W_Len := Length(ARRAY_D);
for I := 0 to Pred(W_Len) do begin
// use W[I] as needed ...
end;
end;
Upvotes: 6