为什么使用枚举类型会出现"type has no typeinfo"错误

25

我声明了以下枚举类型,我希望第一个成员的序数值为1而不是通常的0:

  type
    TMyEnum = (
               meFirstValue = 1,
               meSecondValue,
               meThirdValue
              );

如果我调用 TypeInfo(),例如作为对 GetEnumName() 的调用的一部分,我会收到编译器错误:

  GetEnumName(TypeInfo(TMyEnum), Ord(aValue));

错误: "E2134: 类型'TMyEnum'没有 typeinfo"

为什么会出现这个错误?

我知道只有在启用 $M 编译器选项或派生自某些已经启用该选项的类(例如 TPersistent)的情况下,才会在类中有 typeinfo。但是我认为枚举类型没有任何特殊条件以获得 typeinfo。

3个回答

38

不连续的枚举和从非零值开始的枚举没有类型信息。要实现类型信息,需要采用与现有 tkEnumeration 不同的格式,以解决向后兼容性问题。

我考虑实现一个 tkDiscontiguousEnumeration(或更合适的成员名称)用于 Delphi 2010,但考虑到它们相对较少出现和枚举中的困难——如何有效地编码范围?有些编码对某些情况更好,对其他情况则更差。


6
这是一些有用而有趣的背景信息。你至少应该考虑更新E2134错误的文档。这提供了一个类型信息不生成但不提供关于枚举类型方面注意事项的示例。再说,我经过了近15年的Delphi编程才偶然发现这个问题,所以就像你说的那样,它并不是一个常见的问题。 :) - Deltics

24

当枚举分配了特定的序数值,导致枚举成员的序数值与编译器通常分配的值不同时,不支持类型信息。

如果特定值是必要或理想的,就必须插入“未使用”的枚举成员来“填充”枚举。例如(额外的缩进仅用于强调):

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue
              );

可以使用子范围来“过滤”未使用的初始值:

   TValidMyEnum = meFirstValue..meThirdValue;

尽管如此,您可能希望考虑重命名原枚举类型,以便您的子范围类型可以在整个项目中使用。

如果枚举包含“间隙”,则子范围不足以满足要求:

  type
    TMyEnum = (
                meNOTUSED1,   {= 0}
               meFirstValue,  {= 1} 
               meSecondValue,
               meThirdValue,
                meNOTUSED2,
               meFinalValue   {= 5}
              );
在这种情况下,没有简单的方法可以扩展编译时范围检查以排除未使用的成员,但是一些集合类型将简化实施任何必要的运行时检查的业务:
  type
    TMyEnums = set of TMyEnum;

  const
    meNOTUSED      = [meUNUSED1, meUNUSED2]; //  .. etc as required
    meValidValues  = [Low(TMyEnum)..High(TMyEnum)] - meNOTUSED;


  if NOT (aValue in meValidValues) then
     // etc

2
也许你可以通过使用子范围类型来减轻这种痛苦: type TMyEnumWithDummy = ( meNOTUSED, meFirstValue, meSecondValue, meThirdValue ); TMyEnum = Succ(meNOTUSED)..High(TMyEnumWithDummy); - Uli Gerhardt
2
确实是这样,但如果枚举中存在“间隙”,那么简单的子范围将不足以解决问题。在我遇到这种情况时,我的枚举中存在间隙 - 不幸的是,我在最初的“问题”中过于简化了。但我会采纳您的建议并更新答案。 - Deltics
那么,TDirection = (Negative = -1, None = 0, Positive = 1) 怎么办?有没有建议如何容纳此枚举,以便编译器可以生成适当的类型信息?注意:对我来说,上述方式中的 TDirection 非常舒适,因为我只需将 Ord(TDirection.<SomeMember>) 乘以某个操作数即可在我的项目中进行某些计算得出正确的结果。 - Delmo
1
@Delmo 带有负值的枚举与非连续或非零基础的枚举属于同一类别:这些不是编译器通常分配的值,因此不会为这些值生成类型信息。集合或其他验证替代方案可以在保留“正常”类似编译器的枚举值的同时进行编译时强制执行,但这对于此处无济于事。如果您想在枚举中使用负值,那么现在您只能放弃类型信息(除非/直到Embarcadero解决此限制。当然,他们可能已经解决了。您最近尝试过哪个版本的Delphi?)。 - Deltics
谢谢@Deltics,我正在使用最新版本,即Delphi 10.2.3(东京),它不会为这种枚举生成类型信息,因此我必须更改TDirection定义以适应默认行为,即TDirection =(Negative,None,Positive),然后实现一个实用函数将TDirection值转换为实际值,即TDirection.Negative => -1,TDirection.None => 0和TDirection.Positive => 1。 - Delmo
我也遇到过这个问题,使用从-1开始的枚举。令我惊奇的是,内置调试器能够返回没有TypeInfo的枚举类型变量的正确EnumName。他们是如何做到的?我能在运行时以某种方式访问吗? - H.Hasenack

1
当你想将枚举转换为特定的值(并返回)时,我通常创建一个包含所需值的数组常量,以每个枚举值为基础:
Const MyEnumValues: array[TMyEnum] of integer = (1,2,5);

当枚举展开时,如果您缺少数组值,则会收到编译器错误提示。请注意,更改枚举的顺序时,必须相应地更改值。要获取枚举值的“值”,只需编写:
Value := MyEnumValues[myenum];

要根据“value”获取枚举值,只需循环遍历MyEnumValues的值:

Function GetEnumByValue(value:integer): TMyEnum;
Var
  myenum: TMyEnum;
Begin
  For myenum = low(TMyEnum) to high(TMyEnum) do
    If MyEnumValues[myenum] = value then
      exit(myenum);
  Raise exception.create(‘invalid value for tmyenum’);
End;

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