Como atravessar os enums em qualquer conjunto?

Nov 05 2020

Eu tenho muitos tipos de enum que são acoplados a um conjunto correspondente, como ...

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

Estou tentando criar um único conjunto de funções que possa funcionar em qualquer conjunto de enum, em vez de escrever funções separadas para cada um. Essas funções serão responsáveis ​​por interpretar os valores que estão incluídos em um determinado conjunto.

Para um único conjunto específico, posso ter uma função como esta ...

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;

...e...

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

Como faço para que o acima seja reutilizável para qualquer tipo de enum / set?

Exemplo de uso:

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

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

Respostas

5 RemyLebeau Nov 05 2020 at 01:43

Você pode tentar usar uma mistura de genéricos e RTTI para fazer o que está procurando, por exemplo:

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

Alternativamente:

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

Se isso não funcionar, você provavelmente terá que incluir os tipos enum e set nos parâmetros genéricos, por exemplo:

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