How do I tell which case is valid in a variant record when the case variable isn't named, and how do I create values of such records?

data-structures, delphi, memory-management

Solution

You're not understanding correctly. :-)

The record always has the same layout in memory, and always has both `0` and `1` parts of the `case` section. You don't create one or the other. The one that is used is decided by how you access the record automatically; both cases always exist.

`TRect` is a good example. If you create a new `TRect` using the `Rect` function, you've got a single `TRect` record that has both the `0` and `1` parts available:

var
  R: TRect;
  RTop, RLeft: Integer;
  RTopLeft: TPoint;


  R := Rect(0, 0, 100, 100);
  RTop := R.Top;             // Access TRect.Top separately
  RLeft := R.Left;           // Access TRect.Left separately
  RTopLeft := R.TopLeft;     // Access TRect.TopLeft, which is both Top and Left

  if RTopLeft.X = 0     // This is true, because we created the R rect with 0, 0
                        // as the left and top

  if RTop = 0        // This is also true, for the same reason

Another example of how this works is directly from the documentation. Your code can use the named tag part of the record (`Salaried`) to determine which part of the variant portion it should access. Note that both parts still exist at the same time, though.

type
   TEmployee = record
   FirstName, LastName: string[40];
   BirthDate: TDate;
   case Salaried: Boolean of
     True: (AnnualSalary: Currency);
     False: (HourlyWage: Currency);
 end;

var
  Person: TEmployee;

  Person.FirstName := 'John';
  Person.LastName := 'Smith';
  Person.BirthDate := EncodeDate(1980, 1, 1);

Both the `AnnualSalary` and `HourlyWage` exist in memory; which of the two cases are available depends on which one you access:

  // If this person is paid by the hour
  Person.Salaried := False;
  Person.HourlyWage := 20.00;

  // If this person is paid an annual salary instead
  Person.Salaried := True;
  Person.AnnualSalary := 80000.00;

  // You can access both at the same time. Of course, it wouldn't make
  // any sense to do so in this case.
  if (Person.Salaried and (Person.AnnualSalary > 60000)) then
    DoSomething
  else if (not Person.Salaried) and (Person.HourlyWage > 15.00) then
    DoSomethingElse;

The `tag` name (as pointed out by David Heffernan and Free Consulting in the comments) takes space in the memory allocation.

program TestRec;

{$APPTYPE CONSOLE}

uses
  SysUtils;
type
  TTestNoTagName = record    // No tag name on variant part
    I: Integer;
    case Boolean of             
        True: (X, Y: Integer);
        False: (A, B: Integer;)
  end;

type
  TTestWithTagName = record  // Tag name on variant part
    I: Integer;
    case Dummy: Boolean of
      True: (X, Y: Integer);
      False: (A, B: Integer);
  end;

begin
  WriteLn('TTestNoTagName: ', SizeOf(TTestNoTagName));      // SizeOf(12)
  WriteLn('TTestWithTagName: ', SizeOf(TTestWithTagName));  // SizeOf(16)
  ReadLn;
end.

(And thanks to @davea for spotting the mistake in my initial test code.)

Problem

I've been reading about "case" within records and I still have some open questions about it. (http://www.delphibasics.co.uk/Article.asp?Name=Records) I use the example: ``` type TRect = packed record case Integer of 0: (Left, Top, Right, Bottom: Integer); 1: (TopLeft, BottomRight: TPoint); end; ``` The example basically tells me that the first member/property of `TRect` is an `Integer` (which this `Integer` has no name!). If this (unnamed!) integer is `0` or `1` it just adds the corresponding members: ``` (0 for Left, Top, Right, Bottom) (1 for TopLeft, BottomRight) ``` So let's say there is content in memory which I would like to use this record on. I fairly add a new `type` that represents a pointer to `TRect`. The new record would look like this: ``` type PRect = ^TRect; TRect = packed record case Integer of 0: (Left, Top, Right, Bottom: Integer); 1: (TopLeft, BottomRight: TPoint); end; ``` So the content (in memory) would sorta like this: ``` First Integer = 0 // determines the case! Left = 1 Top = 2 Right = 3 Bottom = 4 ``` This content has a total size of 20 bytes in memory and has the address of X. To output my content I would typecast this address like this: `PRect(X)` Now how do I know my record has the "0" or the "1" case?! How could I also create a record with the case of "0" or "1"? I'm extremely confused by this and would like to know if I understood the use of `case` in records...

Original source