【问题标题】:Why do I get "type has no typeinfo" error with an enum type为什么我会收到枚举类型的“类型没有类型信息”错误
【发布时间】:2019-09-18 03:29:49
【问题描述】:

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

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

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

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

错误:“E2134:类型 'TMyEnum' 没有类型信息”

这是为什么?

我知道类只有在启用 $M 编译器选项的情况下编译或(从某个类派生,例如 TPersistent)才具有类型信息,但我认为枚举类型的 typeinfo 没有任何特殊条件。

【问题讨论】:

    标签: delphi enums rtti typeinfo


    【解决方案1】:

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

    我考虑为 Delphi 2010 实现一个 tkDiscontiguousEnumeration(或可能更好命名的成员),但考虑到它们的相对稀缺性和枚举困难,好处似乎很小 - 你如何有效地编码范围?某些编码在某些情况下更好,在另一些情况下更差。

    【讨论】:

    • 这是一些有用且有趣的背景信息。您可能至少考虑过更新 E2134 错误的文档。这提供了一个不生成 typeinfo 的示例,但没有提供有关枚举类型的这些注意事项的任何线索。再说一次,我花了将近 15 年的坚实的 Delphi'ing 才偶然发现这一点,所以正如你所说,这并不是一个常见的问题。 :)
    【解决方案2】:

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

    如果特定值是必要的或可取的,则必须根据需要插入“未使用”枚举成员以“填充”枚举。例如(仅用于强调的附加缩进):

      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
    

    【讨论】:

    • 也许您可以通过使用子范围类型来减轻这样做的痛苦: type TMyEnumWithDummy = ( meNOTUSED, meFirstValue, meSecondValue, meThirdValue ); TMyEnum = Succ(meNOTUSED)..High(TMyEnumWithDummy);
    • 确实如此,尽管如果枚举中有“间隙”,那么简单的子范围是不够的。在我遇到这种情况的情况下,我有差距 - 不幸的是,我对最初的“问题”过于简化了。但我也会用你的建议更新答案。
    • 那么 TDirection = (Negative = -1, None = 0, Positive = 1) 呢?是否有关于如何容纳此枚举以便编译器可以生成正确的类型信息的任何建议?注意:上述方式的 TDirection 对我来说很舒服,因为我只需要将 Ord(TDirection.) 乘以某个操作数即可在我的项目中的某些计算中得到正确的结果。
    • @Delmo 具有负值的枚举与非连续或基于非零的枚举属于同一类别:这些不是编译器通常会分配的值,因此不会为这些生成类型信息.编译时强制执行的集合或其他验证替代方法,同时保留“正常”的类似编译器的枚举值在这里没有帮助。如果你想要枚举中的负值,那么现在你只需要放弃类型信息(除非/直到 Embarcadero 解决了这个约束。当然,他们可能已经有了。你最近尝试过什么版本的 Delphi?)。
    • 感谢@Deltics,我使用的是最新版本,即 Delphi 10.2.3 (Tokyo),它不会为这种枚举生成类型信息,所以我必须更改 TDirection 定义才能工作使用默认行为,即 TDirection = (Negative, None, Positive) 然后实现一个实用函数将 TDirection 值转换为实际值,即 TDirection.Negative => -1, TDirection.None => 0 和 TDirection.Positive = > 1
    【解决方案3】:

    当您想将枚举转换为特定值(并返回)时,我通常会创建一个数组 const,每个枚举值具有所需的值:

    Const MyEnumValues: array[TMyEnum] of integer = (1,2,5);
    

    这样,当枚举扩展时,你会得到一个编译器错误,说明你缺少一个数组值。

    请注意,更改枚举顺序时,必须相应更改值。

    要获取枚举值的“值”,只需编写:

    Value := MyEnumValues[myenum];
    

    要根据“值”获取枚举值,只需循环 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-11
      • 1970-01-01
      • 2015-06-20
      • 2017-01-04
      • 2019-06-24
      • 2016-04-15
      相关资源
      最近更新 更多