【问题标题】:Is it possible to pass enumeration type as parameter and reuse this type in other functions?是否可以将枚举类型作为参数传递并在其他函数中重用该类型?
【发布时间】:2017-01-07 15:15:27
【问题描述】:

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

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如何接受Command1或Command2或Command3?

更具体地说,delphi 是否可以仅为 Command1 或 Command2 或 Command3 限制房间?意思是如果我放 Command4 它会给我一个编译错误?

【问题讨论】:

  • 你为什么要使用 RTTI?这听起来像是泛型的工作。
  • 什么是theEnumerationType?
  • @DavidHeffernan theEnumerationType 是 CommandSetOne 或 CommandSetTwo 或我注册到 Trobot 的任何枚举类型
  • @RemyLebeau 这是一种泛型,因为发送到 RegisterCommands 的类型可能会改变
  • 你可以查看这个链接,stackoverflow.com/questions/11110704/…

标签: delphi


【解决方案1】:

每当您发现自己想要将某事物的类型作为参数传递时,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;
....

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

删除构造函数并用类构造函数替换它,如下所示:

//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;

【讨论】:

  • 有趣的方法,我非常欣赏 TypeInfo 方法。我将通过 TypeInfo 和 Rtti 的混合来做一个方法
【解决方案2】:

感谢@DavidHeffernan 和@Johan,我混合了这两种方法,并且效果很好。遗憾的是,我仍然无法对此进行合规时间检查。

unit Unit3;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Typinfo, Vcl.StdCtrls;

type
  TRobot = class
    private
      FPTypeInfo : PTypeInfo;
    public
      procedure RegisterCommands<E : record>;
      procedure ExecuteCommands<E : record>(anEnumeration : E);
  end;

  TForm3 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    private
      RobotA : TRobot;
    end;

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

var
  Form3: TForm3;

implementation

{$R *.dfm}

{ TRobot }

procedure TRobot.ExecuteCommands<E>(anEnumeration: E);
  begin
  if (TypeInfo(E) = FPTypeInfo) then
    begin
    showMessage('correct command type');
    end
  else
    begin
    raise Exception.Create('Enum type not correct');
    end;
  end;

procedure TRobot.RegisterCommands<E>;
  begin
  FPTypeInfo := TypeInfo(E);
  end;

procedure TForm3.Button1Click(Sender: TObject);      
  begin
  RobotA := TRobot.Create();
  RobotA.RegisterCommands<CommandSetOne>;
  end;

procedure TForm3.Button2Click(Sender: TObject);
  begin
  RobotA.ExecuteCommands(command1);
  end;

procedure TForm3.Button3Click(Sender: TObject);
  begin
  RobotA.ExecuteCommands(command4);
  end;

end.

【讨论】:

  • 我仍然认为你错了。类的消费者根本不需要对 rtti 做任何事情。这是糟糕的设计。
  • @DavidHeffernan 我认为要么我必须通知 TRobot 类型,要么机器人需要在 rtti 中搜索一些固定名称
  • 你可以按照 Johan 描述的方式使用泛型
  • @DavidHeffernan 很奇怪只发送一个命令来注册我认为的命令集
  • 从类型信息转换为 rtti 类型然后再转换回来,您不觉得奇怪吗?只需使用泛型并获取类型信息。删除注册函数上的参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-12
  • 2020-06-22
  • 1970-01-01
相关资源
最近更新 更多