Delphi Record Helpers For Sets (And Other Simple Types)

Introduced in XE3 - Extend String, Integer, TDateTime, Enumeration, Set, ...

Understanding Delphi Class (and Record) Helpers introduces a feature of the Delphi language allowing you to extend the definition of a class or a record type by adding functions and procedures (methods) to existing classes and records without inheritance.

In XE3 Delphi version, record helpers became more powerful by allowing to extend simple Delphi types like strings, integers, enums, sets and alike.

The System.SysUtils unit, from Delphi XE3, implements a record named "TStringHelper" which is actually a record helper for strings.

Using Delphi XE3 you can compile and use the next code:

var
s : string;
begin
s := 'Delphi XE3';
s.Replace('XE3', 'rules', []).ToUpper;
end;

For this to be possible, a new construct was made in Delphi "record helper for [simple type]". For strings, this is "type TStringHelper = record helper for string". The name states "record helper" but this is not about extending records - rather about extending simple types like strings, integers and alike.

In System and System.SysUtils there are other predefined record helpers for simple types, including: TSingleHelper, TDoubleHelper, TExtendedHelper, TGuidHelper (and a few others). You can get from the name what simple type the helper extends.

There are also some handy open source helpers, like TDateTimeHelper.

Enumerations? Helper for Enumerations?

In all my applications I do frequently use enumerations and sets.

Enumerations and sets being treated as simple types can also now (in XE3 and beyond) be extended with functionality a record type can have: functions, procedures and alike.

Here's a simple enumeration ("TDay") and a record helper:

type
TDay = (Monday = 0, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
TDayHelper = record helper for TDay
function AsByte : byte;
function ToString : string;
end;
And here's the implementation:
function TDayHelper.AsByte: byte;
begin
result := Byte(self);
end;
function TDayHelper.ToString: string;
begin
case self of
Monday: result := 'Monday';
Tuesday: result := 'Tuesday';
Wednesday: result := 'Wednesday';
Thursday: result := 'Thursday';
Friday: result := 'Friday';
Saturday: result := 'Saturday';
Sunday: result := 'Sunday';
end;
end;
And you can have code like this:
var
aDay : TDay;
s : string;
begin
aDay := TDay.Monday;
s := aDay.ToString.ToLower;
end;
Before Delphi XE3 you would probably go with convert a Delphi Enum to a String Representation.

Sets? Helper for Sets?

Delphi's set type is a collection of values of the same ordinal type and a commonly used scenario in Delphi code is to mix both enumerated types and set types.
TDays = set of TDay;
I guess you've used to have code like
var
days : TDays;
s : string;
begin
days := [Monday .. Wednesday];
days := days + [Sunday];
end;
The above code will work with any Delphi version you are using!

BUT, how GREAT would it be to be able to do:

var
days : TDays;
b : boolean;
begin
days := [Monday, Tuesday]
b := days.Intersect([Monday, Thursday]).IsEmpty;
The required implementation would look like:
type
TDaysHelper = record helper for TDays
function Intersect(const days : TDays) : TDays;
function IsEmpty : boolean;
end;
...
function TDaysHelper.Intersect(const days: TDays): TDays;
begin
result := self * days;
end;
function TDaysHelper.IsEmpty: boolean;
begin
result := self = [];
end;
BUT, you see what's wrong here?

For every set type constructed around an enumeration you would need to have a separate helper as, unfortunately, enumerations and sets do not go along generics and generic types.

This means that the following cannot be compiled:

//NO COMPILE OF ALIKE!
TGenericSet = set of <T : [?Enumeration?]>;
However! Something can be done here! We can either do a record helper for a set of bytes or you can checkout TEnum Simple generics Enum example

Record Helper For Set Of Byte!

Having in mind that Delphi sets can hold up to 256 elements and that a Byte type is an integer from 0 to 255, what is possible is the following:
type
TByteSet = set of Byte;
TByteSetHelper = record helper for TByteSet
In an enumeration, like TDay, the actual enumeration values have integer values starting from 0 (if not specified by you differently). Sets can have 256 elements, Byte type can hold values from 0 to 255 and we can think of Enumeration values like Byte values when used in sets.

We can have the following in the definition of the TByteSetHelper:

public
procedure Clear;
procedure Include(const value : Byte); overload; inline;
procedure Include(const values : TByteSet); overload; inline;
procedure Exclude(const value : Byte); overload; inline;
procedure Exclude(const values : TByteSet); overload; inline;
function Intersect(const values : TByteSet) : TByteSet; inline;
function IsEmpty : boolean; inline;
function Includes(const value : Byte) : boolean; overload; inline;
function Includes(const values : TByteSet) : boolean; overload; inline;
function IsSuperSet(const values : TByteSet) : boolean; inline;
function IsSubSet(const values : TByteSet) : boolean; inline;
function Equals(const values : TByteSet) : boolean; inline;
function ToString : string; inline;
end;
And the implementation using standard set type operators:
{ TByteSetHelper }
procedure TByteSetHelper.Include(const value: Byte);
begin
System.Include(self, value);
end;
procedure TByteSetHelper.Exclude(const value: Byte);
begin
System.Exclude(self, value);
end;
procedure TByteSetHelper.Clear;
begin
self := [];
end;
function TByteSetHelper.Equals(const values: TByteSet): boolean;
begin
result := self = values;
end;
procedure TByteSetHelper.Exclude(const values: TByteSet);
begin
self := self - values;
end;
procedure TByteSetHelper.Include(const values: TByteSet);
begin
self := self + values;
end;
function TByteSetHelper.Includes(const values: TByteSet): boolean;
begin
result := IsSuperSet(values);
end;
function TByteSetHelper.Intersect(const values: TByteSet) : TByteSet;
begin
result := self * values;
end;
function TByteSetHelper.Includes(const value: Byte): boolean;
begin
result := value in self;
end;
function TByteSetHelper.IsEmpty: boolean;
begin
result := self = [];
end;
function TByteSetHelper.IsSubSet(const values: TByteSet): boolean;
begin
result := self <= values;
end;
function TByteSetHelper.IsSuperSet(const values: TByteSet): boolean;
begin
result := self >= values;
end;
function TByteSetHelper.ToString: string;
var
b : Byte;
begin
for b in self do
result := result + IntToStr(b) + ', ';
result := Copy(result, 1, -2 + Length(result));
end;
Having the above implementation, the code below happily compiles:
var
daysAsByteSet : TByteSet;
begin
daysAsByteSet.Clear;
daysAsByteSet.Include(Monday.AsByte);
daysAsByteSet.Include(Integer(Saturday);
daysAsByteSet.Include(Byte(TDay.Tuesday));
daysAsByteSet.Include(Integer(TDay.Wednesday));
daysAsByteSet.Include(Integer(TDay.Wednesday)); //2nd time - no sense
daysAsByteSet.Exclude(TDay.Tuesday.AsByte);
ShowMessage(daysAsByteSet.ToString);
ShowMessage(BoolToStr(daysAsByteSet.IsSuperSet([Monday.AsByte,Saturday.AsByte]), true));
end;
I love this. :)

There's a but :(

Note that TByteSet accepts byte values - and any such value would be accepted here. The TByteSetHelper as implemented above is not enumeration type strict (i.e. you can feed it with a non TDay value) ... but as long as I am aware .. it does work for me.

Format
mla apa chicago
Your Citation
Gajic, Zarko. "Delphi Record Helpers For Sets (And Other Simple Types)." ThoughtCo, Jan. 13, 2018, thoughtco.com/record-helpers-for-sets-1058204. Gajic, Zarko. (2018, January 13). Delphi Record Helpers For Sets (And Other Simple Types). Retrieved from https://www.thoughtco.com/record-helpers-for-sets-1058204 Gajic, Zarko. "Delphi Record Helpers For Sets (And Other Simple Types)." ThoughtCo. https://www.thoughtco.com/record-helpers-for-sets-1058204 (accessed May 25, 2018).