Jerry Dodge
Jerry Dodge

Reputation: 27276

How to traverse the enums in any given set?

I have a whole lot of enum types which are coupled with a corresponding set, such as...

type
  TMyEnum = (meOne, meTwo, meThree);
  TMyEnums = set of TMyEnum;

I'm trying to come up with a single set of functions which can work on any enum set, rather than writing separate functions for each and every one. These functions will be responsible for interpreting the values which are included in a given set.

For a single specific set, I may have a function like this...

var
  E: TMyEnum;
begin
  for E := Low(TMyEnum) to High(TMyEnum) do begin
    if E in SomeGivenSet then
      CheckListBox.Checked[Integer(E)]:= True;
  end;
end;

...and...

var
  E: TMyEnum;
begin
  for E := Low(TMyEnum) to High(TMyEnum) do begin
    if CheckListBox.Checked[Integer(E)] then
      SomeGivenSet:= SomeGivenSet + [E];
  end;
end;

How do I accomplish the above to be re-usable for any given enum/set type?

Example usage:

procedure LoadEnums(AEnumType: TAnyEnumType; ASet: TAnySet; AList: TCheckListBox);
procedure SaveEnums(AEnumType: TAnyEnumType; ASet: TAnySet; AList: TCheckListBox);

LoadEnums(TMyEnum, MyEnums, lstMyEnumCheckList);
SaveEnums(TMyEnum, MyEnums, lstMyEnumCheckList);

Upvotes: 3

Views: 436

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595896

You can try using a mix of Generics and RTTI to do what you are looking for, eg:

uses
  ..., CheckLst, TypInfo;

type
  TEnumSerialize<EnumType: record> = class
  private
    class function GetEnumTypeData: PTypeData;
  public
    type SetType = Set of EnumType;
    class procedure LoadEnums(const ASet: SetType; AList: TCheckListBox);
    class procedure SaveEnums(var VSet: SetType; AList: TCheckListBox);
  end;

class function TEnumSerialize<EnumType>.GetEnumTypeData: PTypeData;
var
  TI: PTypeInfo;
begin
  TI := TypeInfo(EnumType);
  if Assigned(TI) and (TI^.Kind = tkEnumeration) then
    Result := GetTypeData(TI)
  else
    Result := nil;
end;

class procedure TEnumSerialize<EnumType>.LoadEnums(const ASet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  AList.CheckAll(cbUnchecked);

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if EnumType(Value) in ASet then
      AList.Checked[Value] := True;
  end;
end;

class procedure TEnumSerialize<EnumType>.SaveEnums(var VSet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  VSet := [];

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if AList.Checked[Value] then
      Include(VSet, EnumType(Value));
  end;
end;
type
  TMyEnum = (meOne, meTwo, meThree);
  TMyEnums = set of TMyEnum;

var
  MyEnums: TMyEnums;

// initialize MyEnums as needed...
TEnumSerialize<TMyEnum>.LoadEnums(MyEnums, lstMyEnumCheckList);
// use lstMyEnumCheckList as needed...
TEnumSerialize<TMyEnum>.SaveEnums(MyEnums, lstMyEnumCheckList);
// save MyEnums as needed...

Alternatively:

uses
  ..., CheckLst, TypInfo;

type
  TEnumSerialize<SetType> = class
  private
    class function GetEnumTypeData: PTypeData;
  public
    class procedure LoadEnums(const ASet: SetType; AList: TCheckListBox);
    class procedure SaveEnums(var VSet: SetType; AList: TCheckListBox);
  end;

class function TEnumSerialize<SetType>.GetEnumTypeData: PTypeData;
var
  TI: PTypeInfo;
begin
  Result := nil;

  TI := TypeInfo(SetType);
  if not (Assigned(TI) and (TI^.Kind = tkSet)) then Exit;

  TD := GetTypeData(TI);
  if not (Assigned(TD^.CompType) and (TD^.CompType^.Kind = tkEnumeration)) then Exit;

  Result := GetTypeData(TD^.CompType^);
end;

class procedure TEnumSerialize<SetType>.LoadEnums(const ASet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  AList.CheckAll(cbUnchecked);

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if Value in ASet then
      AList.Checked[Value] := True;
  end;
end;

class procedure TEnumSerialize<SetType>.SaveEnums(var VSet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  VSet := [];

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if AList.Checked[Value] then
      Include(VSet, Value);
  end;
end;
type
  TMyEnum = (meOne, meTwo, meThree);
  TMyEnums = set of TMyEnum;

var
  MyEnums: TMyEnums;

// initialize MyEnums as needed...
TEnumSerialize<TMyEnums>.LoadEnums(MyEnums, lstMyEnumCheckList);
// use lstMyEnumCheckList as needed...
TEnumSerialize<TMyEnums>.SaveEnums(MyEnums, lstMyEnumCheckList);
// save MyEnums as needed...

If that doesn't work, you will likely have to include both enum and set types in the Generic parameters, eg:

uses
  ..., CheckLst, TypInfo;

type
  TEnumSerialize<EnumType, SetType> = class
  private
    class function GetEnumTypeData: PTypeData;
  public
    class procedure LoadEnums(const ASet: SetType; AList: TCheckListBox);
    class procedure SaveEnums(var VSet: SetType; AList: TCheckListBox);
  end;

class function TEnumSerialize<EnumType, SetType>.GetEnumTypeData: PTypeData;
var
  TI: PTypeInfo;
begin
  Result := nil;

  TI := TypeInfo(SetType);
  if not (Assigned(TI) and (TI^.Kind = tkSet)) then Exit;

  TD := GetTypeData(TI);
  if not (Assigned(TD^.CompType) and (TD^.CompType^ = TypInfo(EnumType)) and (TD^.CompType^.Kind = tkEnumeration)) then Exit;

  Result := GetTypeData(TD^.CompType^);
end;

class procedure TEnumSerialize<EnumType, SetType>.LoadEnums(const ASet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  AList.CheckAll(cbUnchecked);

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if EnumType(Value) in ASet then
      AList.Checked[Value] := True;
  end;
end;

class procedure TEnumSerialize<SetType>.SaveEnums(var VSet: SetType; AList: TCheckListBox);
var
  TD: PTypeData;
  Value: Integer;
begin
  VSet := [];

  TD := GetEnumTypeData;
  if not Assigned(TD) then Exit;

  for Value := TD^.MinValue to TD^.MaxValue do
  begin
    if AList.Checked[Value] then
      Include(VSet, EnumType(Value));
  end;
end;
type
  TMyEnum = (meOne, meTwo, meThree);
  TMyEnums = set of TMyEnum;

var
  MyEnums: TMyEnums;

// initialize MyEnums as needed...
TEnumSerialize<TMyEnum, TMyEnums>.LoadEnums(MyEnums, lstMyEnumCheckList);
// use lstMyEnumCheckList as needed...
TEnumSerialize<TMyEnum, TMyEnums>.SaveEnums(MyEnums, lstMyEnumCheckList);
// save MyEnums as needed...

Upvotes: 6

Related Questions