【问题标题】:Delphi - Create class from a stringDelphi - 从字符串创建类
【发布时间】:2011-04-24 19:38:40
【问题描述】:

我得到了这样的代码

name := 'Foo';
If name = 'Foo' then
  result := TFoo.Create
else if name = 'Bar' then 
  result := TBar.Create
else if name = 'FooFoo' then
  result := TFooFoo.Create;

有什么办法吗

result := $name.create

或基于变量值创建类的某种方式?

所有类都扩展了同一个基类。

【问题讨论】:

  • 谢谢马特,你打败了我
  • 你使用的是哪个版本的Delphi?
  • 重复:Is there a way to instantiate a class by its name in delphi? 没有近距离投票,因为该问题是在 Delphi 增强 RTTI 之前提出的。
  • OP 似乎很困惑。代码示例显示“按名称创建类的实例”,但问题标题显示“从字符串创建类”,这是一个不同且可能没有用的东西。

标签: delphi oop object


【解决方案1】:

从 Delphi 2010 开始,增强的 RTTI 允许您执行此操作,而无需创建自己的类注册表。

使用RTTI 单元,您有多种选择。

对于无参数构造函数,最简单的方法之一是。

var
 C : TRttiContext;
 O : TObject;
begin
  O := (C.FindType('UnitName.TClassName') as TRttiInstanceType).MetaClassType.Create;
  ...
 end;

这里是一个传递参数的例子,使用TRttiMethod.Invoke()

var
 C : TRttiContext;
 T : TRttiInstanceType;
 V : TValue;

begin
  T := (C.FindType('StdCtrls.TButton') as TRttiInstanceType);
  V := T.GetMethod('Create').Invoke(T.metaClassType,[self]);
  (V.AsObject as TWinControl).Parent := self;
end;

我在RTTI 单元上写了several articles,因为有很多可用选项。


更新基于大卫的要求:

比较使用类类型(虚拟构造函数)与TRttiType.Invoke进行构造的用法

类类型方法:(虚拟构造函数)

  • 适用于所有版本的 Delphi
  • 生成更快的代码
  • 在编译时需要祖先的知识。
  • 需要类注册表以通过字符串名称查找类(如 RRUZ 提到的)

TRttiType.Invoke() 方法

  • 仅适用于 Delphi 2010 或更高版本。
  • 较慢的代码
  • 实现一个将名称冲突考虑在内的类注册表
  • 在编译时要求NO了解祖先。

我个人发现每个都有不同的目的。如果我预先知道所有类型,我会使用类类型方法。

【讨论】:

  • 但是,请注意,您最好寻找一个名为 Create 的构造函数,因为 Robert 的代码将简单地调用 TObject.Create 的构造函数,如果 TClassName 的构造函数在一切,都是错误的做法。实例将无法正确初始化。
  • 为什么这比使用虚拟构造函数的老式方法更好?
  • @David:如果您事先了解课程的所有详细信息,则没有任何好处。 RTTI 的好处是能够在运行时确定需要哪些参数并允许您传递它们。它也适用于不能从公共根类型继承的类结构。
  • @Robert 在这种情况下,OP 正在接受一个从共同根下降的类。我认为您至少应该在回答中指出,为 OP 的用例调用 Invoke 并不是最好的解决方案。
  • @David:为了完整起见,我深入比较了这两种方法。
【解决方案2】:

您可以使用GetClass 函数,但在注册类之前必须使用RegisterClassRegisterClasses 方法。

GetClass(const AClassName: string): TPersistentClass;

【讨论】:

  • 这如何创建一个类??
  • 它为您提供类,因此您可以调用调用创建。 GetClass('Name').Create;
  • @Johan,我不明白您的评论和反对意见,您是否阅读过有关 GetClassfunction 的文档?
  • 简而言之,我投了反对票,因为它没有回答如何创建一个类。它只输入问题的一部分。再加一点文字,我相信您可以回答完整的问题。另外我认为必须注册课程并不好。最后你什么也没说静态与虚拟构造函数。哦,带参数和不带参数的构造函数呢?
  • @Johan:你不能在运行时创建类...如果你喜欢挑剔,你可以创建类instances,不是上课。此外,无论您想使用什么技术来获取类引用来创建实例,您都需要在类引用和您想用来检索它们的任何标识符之间进行某种映射。除非需要以非常通用的方式工作,否则恕我直言 RTTI 是使代码不可读(且速度慢)的最佳方式。但 RTTI 也将名称“注册”到相应的元数据和代码中。无论如何,与其抱怨别人的答案,不如提供你自己的答案……
【解决方案3】:

通常的方法是使用虚拟构造函数。 TComponent 就是一个很好的例子,你一定很熟悉。

TComponent 具有以下构造函数:

constructor Create(AOwner: TComponent); virtual;

另一个关键是TComponentClass,它被声明为class of TComponent

当 VCL 流式传输 .dfm 文件时,它会从 .dfm 文件中读取类的名称,并通过一些我们不需要在这里介绍的过程将该名称转换为变量,ComponentClass 说是类型TComponentClass。然后它可以实例化对象:

Component := ComponentClass.Create(Owner);

这是拥有虚拟构造函数的一大优势,我鼓励您采用相同的方法。

如果您必须使用字符串来识别类,那么您仍然需要提供一个查找例程来将字符串类名转换为类引用。如果方便,您可以挂钩到 TComponent 使用的同一 VCL 机制,即 RegisterClass

或者,如果您可以将代码中的 name 替换为类引用,那么您可以编写:

type
  TFoo = class
    constructor Create; virtual;
  end;
  TBar = class(TFoo);

  TFooClass = class of TFoo;

var
  MyClass: TFooClass;

...

MyClass := TFoo;
result := MyClass.Create;//creates a TFoo;

MyClass := TBar;
result := MyClass.Create;//creates a TBar;

【讨论】:

  • +1 这是我的首选方法,即使您必须编写自己的查找代码(一个简单的 TStringList 与 Objects 中的类引用通常绰绰有余),因为它避免了 RTTI 的膨胀和特定类的要求作为您自己的类层次结构的根。
  • @marjan 您可以使用系统注册表之一,但这样做会引入对命名空间的担忧。最重要的是了解虚拟构造函数,这是一个非常细微的差别,其他答案都没有提到。
  • 是的,虚拟构造函数确实是使用类引用的关键。虚拟构造函数对我来说是如此“自然”,以至于我忘记了并非所有语言都有它们和/或并非所有程序员都熟悉它们。感谢您引起我的注意。这将有助于我在工作中担任教练。
  • 我曾在应用程序中使用它来减少从主窗体到新窗体的循环引用(因为我喜欢做 MDI GUI)。当它真的需要时,我只有在使用子句中引用辅助表单单元的主表单。让它变得更小更清晰...... ;-)
  • +1 不错的干净答案。建议在示例中演示result 的类型。例如,如果TFoo 有一个基类(比如)TForm,如TFoo = class(TForm),则声明为result: TForm;
猜你喜欢
  • 1970-01-01
  • 2017-05-18
  • 1970-01-01
  • 2020-07-30
  • 1970-01-01
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
相关资源
最近更新 更多