Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

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...

like image 874
Ben Avatar asked Dec 08 '13 06:12

Ben


2 Answers

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.)

like image 140
Ken White Avatar answered Oct 20 '22 20:10

Ken White


What is missing from the previous answer as well as from the on-line documentation is an important fact what:

tag field (if present) as well as its type plays absolutely(!) no role in the accessing fields of the variant part of record. Moreover, present tag field treated the same way as regular field declared outside of variant part, so for example both TEmployee.AnnualSalary and TEmployee.HourlyWage variant fields can be accessed independently of the value of TEmployee.Salaried tag field. The type of tag is arbitrarily chosen ordinal type and can be used for self-documentation purposes, as demonstrated in TFigure example.


Answering to another misconception might be developed while reading delphibasic text:

All of variant part of record occupy the same memory region, so the size of whole group of variant parts in memory equals to the size of largest particular variant part in group.

{ this record occupies SizeOf(Word) = 2*SizeOf(Byte) bytes in memory }
TWordCracker = packed record
  case Integer of
    0: (W: Word);
    1: (Lo, Hi: Byte);
    2: (Bytes: packed array [0..1] of Byte);
end;
like image 2
Free Consulting Avatar answered Oct 20 '22 20:10

Free Consulting