如何遍历任意给定集合中的枚举?

3

我有很多枚举类型,它们与相应的集合耦合在一起,例如...

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

我试图设计一组通用的函数,可以适用于任何枚举集合,而不是为每一个枚举集合编写单独的函数。这些函数将负责解释给定集合中包含的值。

对于一个特定的集合,我可能会有以下类似的函数...

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;

...和...

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

我该如何实现上述内容以便于适用于 任何 枚举/集合类型?

示例用法:

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

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

链接,并将集合解释为位域是可能的解决方案。 - ub_coding
@ub_coding 获取类型信息不是问题。我需要帮助的是分配给集合的值 - Jerry Dodge
@DelphiCoder 不是,否则我可能不需要问问题 :-) - Jerry Dodge
1
https://stackoverflow.com/a/59330090/11329562 - Delphi Coder
1
我认为你在这里不会得到一个类型安全的解决方案,因为 Delphi 泛型约束的限制。也许你能得到的最接近的解决方案是对枚举和集合类型进行参数化,然后使用运行时检查,例如断言,确保集合类型确实是指定枚举的集合。我对你的悬赏有问题,因为我认为你的范围不够明确。我可以想象花时间开发某些东西,但范围却改变了。此外,如果 Remy 的答案中的代码无法编译,你可以修复它。你有足够的技能来做到这一点。 - David Heffernan
显示剩余9条评论
1个回答

6
您可以尝试使用泛型和运行时类型识别(RTTI)的组合来实现您想要的功能,例如:
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...

或者:

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

如果那样不起作用,你可能需要在通用参数中包含枚举类型和集合类型,例如:
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...

非常好。在我尝试实现之前,我只是猜测/假设可能有一种方法可以仅传递集合,并自动确定枚举类型,而无需同时传递两者? - Jerry Dodge
在另一种情况下... if Value in ASet then - E2015 运算符不适用于此操作数类型 - Jerry Dodge
1
E2001 需要序数类型 ... E2015 运算符不适用于此操作数类型 - 我就知道会出现这种情况。我现在没有安装编译器来验证语法,你只能自己试试了。你可能无法避免在泛型参数中指定两种类型。 - Remy Lebeau
1
现在是时候玩一下了……“如果雷米想不出来,谁能想出来呢?” :-) 感谢您的努力,毫无疑问这些障碍迟早会被克服。 - Jerry Dodge
Stefan Glienke 可能可以 ;-) - Delphi Coder
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接