Lucky J C
Lucky J C

Reputation: 1

Pascal - How to refer to a field of a type with a string variable?

For example I have a type :

type_test =record
    x : Integer;
    y : Integer;
    z : Integer;
end;

var
    test_var : type_test;
    test_str : string;

How do I assign the value of test_var.x, y, and z using test_str? It would be great if something like this can work :

test_str := x;
test_var.test_str := 99;
writeln(test_var.x); //outputs 99

However, this doesn't work. Is there any possible way to achieve this? I need to assign myriads of fields within a data type, and it would be great if I can use a loop with an array of field names using this method.

Upvotes: 0

Views: 1228

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595349

The short answer is that the Pascal language (whether Delphi or FreePascal variant of it) simply does not support the kind of syntax that you are looking for. You cannot substitute a member name with a string populated at runtime.

However, you can use an Advanced Record that contains a property to help you achieve something similar to what you want, eg:

{$ModeSwitch AdvancedRecords}

type
  type_test = record
  private
    function GetFieldPtr(const FieldName: String): PInteger;
    function GetFieldValue(const FieldName: String): Integer;
    procedure SetFieldValue(const FieldName: String; AValue: Integer);
  public
    x : Integer;
    y : Integer;
    z : Integer;
    property FieldValue[const FieldName: string]: Integer read GetFieldValue write SetFieldValue default;
  end;

function type_test.GetFieldPtr(const FieldName: String): PInteger;
begin
  // TODO: alternatively, you can use RTTI here to find a field by name...
  //
  // See "FPC : RTTI on records"
  // https://stackoverflow.com/questions/27803383/
  //
  if FieldName = 'x' then
    Result := @x
  else if FieldName = 'y' then
    Result := @y
  else if FieldName = 'z' then
    Result := @z
  else begin
    Result := nil;
    raise Exception.CreateFmt('Unknown Field: "%s"', [FieldName]);
  end;
end;

function type_test.GetFieldValue(const FieldName: String): Integer;
var
  FieldPtr: PInteger;
begin
  FieldPtr := GetFieldPtr(FieldName);
  Result := FieldPtr^;
end;

procedure type_test.SetFieldValue(const FieldName: String; AValue: Integer);
var
  FieldPtr: PInteger;
begin
  FieldPtr := GetFieldPtr(FieldName);
  FieldPtr^ := AValue;
end;

Now you can do this:

var
  test_var : type_test;
  test_str : string;
begin
  test_str := 'x';
  test_var[test_str] := 99;
  WriteLn(test_var.x); //outputs 99
end;

Things get a little trickier if your record contains fields of different data types. In which case, your property will have to use Variant or TValue instead.

Upvotes: 0

Deltics
Deltics

Reputation: 23036

What you appear to want and need is simply a string property declared on your record type that can both represent all of the member data of the record as a string, but also allow you to set it, presumably according to some formatting rules. I anticipate the following, for example:

test_var.AsString := '99';         //  Sets x = 99
test_var.AsString := '99,42';      //  Sets x = 99 and y = 42
test_var.AsString := '99,42,57';   //  Sets x = 99, y = 42 and z = 57

One way would simply involve a property declared on your record type which performs and interprets the desired formatting when reading or writing a variable of that record type using that property, in this case, as a single string value:

type_test = record
private
  function get_AsString: String;
  procedure set_AsString(const aValue: String);
public
  x : Integer;
  y : Integer;
  z : Integer;
  property AsString: String read get_AsString write set_AsString;
end;


function type_test.get_AsString: String;
begin
  // Return x, y and z formatted as a single string value
end;


procedure type_test.set_AsString(const aValue: String);
begin
  // Unpacks values in the supplied formatted string and 
  //  applies them to x, y, and as appropriate
end;

The implementation details of these methods is left as an exercise for you (apart from anything else the precise specification for the representation of your record type as a String value is not provided).

The application of RTTI might allow your string format to be more generic/flexible:

test_var.AsString := 'z=99';         //  Sets z = 99

You could still support this in the AsString property-based approach as above, simply incorporating specific field naming in your read/write methods. RTTI is only necessary for complete flexibility, i.e. to allow your read/write methods to 'discover' the supported member fields rather than being coded explicitly to support specific named members.

Any methods for reading/writing values of a particular record type do not necessarily need to be part of the record type itself (although this in turn makes those methods less discoverable by consumers of the record type).

The argument for explicit AsString support on a type is stronger than generalised methods for reading/writings record members to/from a string. Explicit support involves knowledge of implementation details, tightly coupling the AsString methods to the type it supports. Where-as, by definition, a completely generalised method for reading/writing members of a record by name could in theory be applied to any record type, so making them part of any specific record type makes little sense.

RTTI based approaches will also be slightly less efficient due to the additional work involved in discovery using RTTI, at runtime.

In short, RTTI provides greater flexibility at the cost of complexity and some overhead.

Whether that complexity and overhead is a fair exchange for the flexibility is up to you.

Upvotes: 1

Related Questions