Peter Jones
Peter Jones

Reputation: 745

Delphi - point to a const array of TRecord?

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

Answers (2)

Dsm
Dsm

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.

EDIT

Have shown how to iterate. I have added a property called Count and shown in the test routine how to iterate

Upvotes: 0

Remy Lebeau
Remy Lebeau

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 or PWideChar and the other is a zero-based character array of the form array[0..n] of PAnsiChar or PWideChar.
  • 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 or PChar.
  • 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 or PWideChar and T2 is a zero-based character array of the form array[0..n] of Char (when T1 is PAnsiChar) or of WideChar (when T1 is PWideChar).
  • 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 or OleVariant type.
  • T1 is an OleVariant and T2 is an integer, real, string, character, Boolean, interface, or Variant type.
  • T1 is an integer, real, string, character, or Boolean type and T2 is Variant or OleVariant.
  • T1 is the IUnknown or IDispatch interface type and T2 is Variant or OleVariant. (The variant's type code must be varEmpty, varUnknown, or varDispatch if T1 is IUnknown, and varEmpty or varDispatch if T1 is IDispatch.)

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

Related Questions