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

时间:2016-08-31 06:54:30

标签: 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. 我应该为theEnumerationType使用哪种类型?

  2. 如果不能使用此枚举,任何其他使用枚举的解决方案?

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

  4. 编辑:

    感谢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

    更具体地说,德尔福可以仅为 Command1 Command2 Command3 限制房间?意味着如果我把 Command4 它给我一个编译错误?

2 个答案:

答案 0 :(得分:2)

每当你发现自己想要将某种类型的东西作为参数传递时,goto解决方案就是泛型。

我们将滥用枚举实际上是一个整数的事实 假设您在枚举标签的字符串表示中编码了实际命令 e.g。

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;

答案 1 :(得分:1)

感谢@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.