delphi - 是否可以将枚举类型作为参数传递并在其他函数中重用该类型?

标签 delphi

我想要实现的就像虚拟代码一样:

type
  CommandSetOne = (Command1, Command2, Command3);
  CommandSetTwo = (Command4, Command5, Command6);

  TRobot = class
    procedure RegisterCommands(anyEnumerationType : TRttiEnumerationType);
    procedure ExecuteCommands(anEnumeration : theEnumerationType);
  end;

我可能有多个命令集,并且命令集中的任何命令都是可替换的。

TRobot有一个过程可以接受一个枚举类型作为参数,他会保存这个类型,并在ExecuteCommands过程中使用这个类型。

关于传递任何枚举类型作为参数,我发现一种方法是使用 TRttiEnumerationType,在调用端它应该如下所示:

var
  rttiContext : TRttiContext;
  typeref : TRttiType;
  RobotA : TRobot;
begin
  rttiContext := TRttiContext.Create();
  RobotA := TRobot.Create();
  RobotA.RegisterCommands(rttiContext.GetType(TypeInfo(CommandSetOne)));
end;

但我在传递像 Command1 这样的命令时遇到了困难。我已经尝试过 theEnumerationType 的 Variant,但似乎无法将 Command1 传递给它。

我知道如果我使用像 TStringList 这样的东西,这是一种更简单的方法来完成我想要的事情,但我想在遵守时由 delphi 检查,以防我输错一些命令(使用 TstringList 我可以添加代码在运行时检查)

所以真正的问题是:

  1. 我应该为 EnumerationType 使用哪种类型?

  2. 如果不可能,还有其他使用枚举的解决方案吗?

  3. 或者任何解决方案可以提供合规时间检查以及灵活的结构?

编辑:

感谢 David 的建议,我应该同时使用 Rtti 的东西,所以为了清楚起见,我添加了 RegisterCommands 的实现

implementation
  procedure TRobot.RegisterCommands(anyEnumerationType : TRttiEnumerationType);
    begin
    theEnumerationType := anyEnumerationType;
    end;
  procedure TRobot.ExecuteCommands (anyEnumerationValueoftheType : ???);
    begin
    //do something with the command
    end;

那么什么应该适合该类型的任何枚举值?

例如,如果我在 RegisterCommands 中使用 CommandSetOne, delphi如何接受Command1Command2Command3

更具体地说,delphi可以仅限制Command1Command2Command3的空间吗?意味着如果我输入 Command4 它会给我一个编译错误?

最佳答案

每当您发现自己想要将某些内容的类型作为参数传递时,goto 解决方案就是泛型。

我们将滥用枚举实际上是下面的整数这一事实。
假设您有以枚举标签的字符串表示形式编码的实际命令。
例如

TCommands = (Left, Right, Up, Down);

TRobot = class
private
  FRegisteredCommands: TDictionary<integer, string>;
public  
  procedure RegisterCommand<E: record>(Enum : E);
  procedure ExecuteCommand<E: record>(Enum : E);
end;

procedure TRobot.RegisterCommand<E: record>(Enum: E);
var
  Key: integer absolute Enum;  //typesafe, because of the if below.  
  Info: PTypeInfo;
begin
  if GetTypeKind(E) <> tkEnumeration then raise Exception.Create('Enum is not an enum');
  //Added type safety:
  if     not(TypeInfo(E) = TypeInfo(TRobotCommand1)) 
      or not(TypeInfo(E) = TypeInfo(TRobotCommend2)) then raise ....
  Info:= TypeInfo(Enum);
  FRegisteredCommands.Add(Key, GetEnumName(Info, Key));
end;

The compiler will remove all this if code if these checks are true and only generate the code if these checks are false, because GetTypeKind is a compiler intrinsic routine这意味着执行这些检查将花费零运行时间
请注意,如果您喜欢快速的性能,则可以使用 if TypeInfo(E) = TypeInfo(TMyCommandSet) 编译器内在函数技巧对命令进行硬编码。

请注意,在早期的 Delphi 中,absolute 指令会导致编译器内部错误(在西雅图,它可以 100% 正常工作)。在这种情况下,请像这样更改代码:

procedure TRobot.RegisterCommand<E: record>(Enum: E);
var
  Key: integer;
  Info: PTypeInfo;
begin
  ....
  Key:= PInteger(@Enum)^;
  .....

如果给定的 TRobot 后代仅接受单一类型的命令,那么我会将通用类型移动到 TRobot,如下所示:

TBaseRobot<E: record> = class(TObject)
   constructor Create; virtual;
   procedure RegisterCommand(Enum: E);  //only implement once, see above.
   procedure ExecuteCommand(Enum: E); virtual; abstract; //implement in descendents.
....

constructor TBaseRobot<E>.Create;
begin
  inherited Create;
  if GetTypeKind(E) <> tkEnumeration then raise('error: details');  
end;

TRobotA = class(TBaseRobot<TMyEnum>)
  procedure ExecuteCommand(Enum: TMyEnum); override;
end;
....

编辑
您可以在类构造函数中进行检查,而不是在构造函数中进行检查。这样做的好处是,任何错误都会在您的应用程序启动后立即触发,而不是在测试中可能永远不会发生的某个随机时间触发。

删除构造函数并将其替换为类构造函数,如下所示:

//You should never name a class constructor `create`. class constructor don't create anything, they init stuff.
class constructor TBaseRobot<E>.Init;  
begin
  if GetTypeKind(E) <> tkEnumeration then raise('error: details');  
end;

关于delphi - 是否可以将枚举类型作为参数传递并在其他函数中重用该类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39242690/

相关文章:

multithreading - 默认 VCL 应用程序中还有哪些其他线程?它们可以按用途命名吗?

json - 如何使用 SuperObject 序列化包含点(例如 IP 地址)的 JSON key ?

Delphi - 我如何告诉系统使用哪台打印机?

Delphi 2010 搜索环绕

Delphi - 如何防止应用程序在写入 LPT 端口时挂起

email - Delphi/Indy 10 - 发送带有附件的文本或 HTML 电子邮件会为文本(正文)本身添加一个附件

delphi - TMemo 的 CueText 等效项

delphi - 当我调用代码格式化程序时,如何避免扩展折叠区域?

delphi - 什么时候在 Delphi 中使用 is 运算符是正确的?

delphi - 在运行时,DBGrid 列中的条目号 (1.30) 自动转换为 (1 :30) within the RangeArray(1. 00-11.59)