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...
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.)
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;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With