枚举范围检查

Enumeration Range Checking

Delphi 不直接支持将整数类型转换为枚举的任何范围检查或超出范围的异常引发。参见:How do I convert an integer to an enumerated type? 我试图通过自动范围检查在枚举子范围上索引的数组来解决这个问题。在我的测试程序中,我故意使用了一个非连续枚举,它禁用了 RTTI,并为没有命名枚举但可以通过类型转换、Inc、Dec 等访问的有效子范围值提供测试

程序按预期处理子范围 (2) 的非枚举元素,但不会为枚举数组生成超出范围的异常 - 但会为等效的整数索引数组生成异常。这有什么充分的理由吗?我可以通过使用整数索引数组来解决,但枚举索引会更健壮一些。

type MyEnum = (zero,one,three=3);
var EnumCheck: array[MyEnum] of integer = (0,1,2,3);
var iEnum: integer;
var MyEnumVar: MyEnum;
var IntArray: array[0..3] of integer = (0,1,2,3);

procedure Test;
begin
{$R+}
  MyEnumVar:= MyEnum (1);         // One
  iEnum := EnumCheck[MyEnumVar];  // OK - iEnum = 1
  MyEnumVar:= MyEnum (2);         // Out-of-bound
  iEnum := EnumCheck[MyEnumVar];  // OK - iEnum = 2
  MyEnumVar:= MyEnum (3);         // Three
  iEnum := EnumCheck[MyEnumVar];  // OK - iEnum = 3
  MyEnumVar:= MyEnum (4);         // Out-of-bound
  iEnum := EnumCheck[MyEnumVar];  // no Exception; iEnum is set to random value
  iEnum := 4;
  iEnum := IntArray[iEnum];      // Exception thrown here
  iEnum := IntArray[4];          // Compiler "subrange" error
end;

使用 Delphi 10.4 更新 2

编辑:我找到了解决办法。将校验数组定义为:

var iCheck: array[Ord(Low(MyEnum))..Ord(High(MyEnum))] of integer = (0,1,2,3);

并按预期抛出超出范围的异常。

我已经使用枚举记录助手将我的 work-around 实现为一个相对简洁的解决方案。

type MyEnum = (Zero, One, Two, Three);

type MyEnumHelper = record helper for MyEnum
  function SetInRange(const Value: integer): boolean;
end;

function MyEnumHelper.SetInRange(const Value: integer): boolean;
var Check: array[Ord(Low(MyEnum))..Ord(High(MyEnum))] of integer;
begin
  Result := true;
  Self := MyEnum(Value);
{$UNDEF _RANGEOFF}
{$IFOPT R-} {$DEFINE _RANGEOFF} {$R+} {$ENDIF}
  try
    var iCheck := Check[Value];
  except on ERangeError do
    Exit(false);
  end;
{$IFDEF _RANGEOFF} {$R-} {$ENDIF}
end;

// Usage

function SetEnums(const Val1, Val2, Val3: integer): boolean
var MyEnumVar1, MyEnumVar2, MyEnumVar3: MyEnum;
begin
  if (not MyEnumVar1.SetInRange(Val1)) then Exit(false);
  if (not MyEnumVar2.SetInRange(Val2)) then Exit(false);
  if (not MyEnumVar3.SetInRange(Val3)) then Exit(false);
end;

注意。定长数组(Check)不需要手动初始化,我们感兴趣的都在范围内。

对于 non-continuous 枚举(正如我在原始示例中使用的那样),没有完全可靠的方法来使用它,因为“匿名”枚举仍然是 in-range。如果你到了使用助手中的实际生成值来解决这个问题的地步,它就绕过了首先使用助手的要点。