我想要实现的就像虚拟代码一样:
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我可以添加代码在运行时检查)
所以真正的问题是:
我应该为theEnumerationType使用哪种类型?
如果不能使用此枚举,任何其他使用枚举的解决方案?
或任何解决方案都可以提供合规时间检查以及灵活的结构?
编辑:
感谢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 它给我一个编译错误?
答案 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)
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.