19

I got code like this

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;

Is there a way just to do

result := $name.create

or some way of creating class based of a variable value?

All the classes extended the same base class.

Mat
  • 202,337
  • 40
  • 393
  • 406
Wizzard
  • 12,582
  • 22
  • 68
  • 101
  • Thanks Mat, you beat me to the formating – Wizzard Apr 24 '11 at 19:40
  • which version of Delphi are you using? – Johan Apr 24 '11 at 22:07
  • 1
    Duplicate: [Is there a way to instantiate a class by its name in delphi?](http://stackoverflow.com/questions/701049/) No close vote since that question is before Delphi had enhanced RTTI. – Sertac Akyuz Apr 25 '11 at 00:18
  • The OP seems confused. The code sample shows "creating an instance of a class, by name" but the question title says "create class from a string" which is a different and probably not useful thing. – Warren P Apr 25 '11 at 02:24

3 Answers3

29

Starting with Delphi 2010, the enhanced RTTI allows you do this without having to creating your own Class Registry.

Using the RTTI Unit you have several options available.

For Parameter Less Constructors one of the easiest is.

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

Here is an example of passing a parameter, using the 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;

I wrote several articles on the RTTI unit as there is many options available.


Updated Based on David Request:

Comparing the usage of construction using the Class Type (Virtual Constructor) with the TRttiType.Invoke

Class Type Method: (Virtual Constructor)

  • Works in all version of Delphi
  • Produces Faster Code
  • Requires knowledge of ancestry at compile time.
  • Requires a Class Registry to look up a Class by a String Name (Such as mentioned by RRUZ)

TRttiType.Invoke() method

  • Only works in Delphi 2010 or later.
  • Slower code
  • Implements a Class Registry that takes Name conflicts into account
  • Requires NO knowledge of ancestry at compile time.

I personally find each serves a different purpose. If I know all the types up front the I use the Class Type Method.

Community
  • 1
  • 1
Robert Love
  • 12,447
  • 2
  • 48
  • 80
  • 2
    Mind, however, that you are much better off looking for a constructor called Create, because Robert's code will simply call `TObject.Create`'s constructor, which, if `TClassName`'s constructor does anything at all, is the wrong thing to do. The instance won't be initialized properly. – Barry Kelly Apr 24 '11 at 20:28
  • 1
    why is this better than the old fashioned method using virtual constructors? – David Heffernan Apr 25 '11 at 08:24
  • 1
    @David: If you know all the details of your classes up front then there is no benefit. The RTTI, offers the benefit of being able to determine at runtime what parameters are required and allowing you to pass them. It also works well with class structures that can't descend from a common root type. – Robert Love Apr 25 '11 at 12:59
  • @Robert In this case the OP is taking about a class that descends from a common root. I think you ought to at least point out in your answer that calling Invoke for the OP's use case is not the best solution. – David Heffernan Apr 25 '11 at 13:23
  • @David: Just to be complete, I compared both methods in depth. – Robert Love Apr 25 '11 at 14:19
  • @Robert A comparison of the compiled code is not what I meant. I also find it rather uninteresting. What matters here, and almost everywhere, are issues of maintainability, correctness, ease of development etc. That's why, for the OP's use case, it is better to keep within the type system and avoid `Invoke()`. – David Heffernan Apr 25 '11 at 14:24
  • I stripped the ASM and broke it out a bit better. – Robert Love Apr 25 '11 at 14:39
  • @Robert Sorry to whine on so much, but it's much better now in my opinion. +1 – David Heffernan Apr 25 '11 at 18:41
  • @David, No big deal... Rather have it complete. – Robert Love Apr 25 '11 at 19:42
16

You can use the GetClass function, but before you must register the classes using the RegisterClass or RegisterClasses methods.

GetClass(const AClassName: string): TPersistentClass;
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • How does this **create** a class?? – Johan Apr 24 '11 at 22:05
  • 10
    It gives you the class so you can call call create. `GetClass('Name').Create;` – Robert Love Apr 24 '11 at 22:15
  • 4
    @Johan, I don't understand your comment and downvote, do you read the documentation about the `GetClass`function? – RRUZ Apr 24 '11 at 22:17
  • Briefly, I downvoted because it does not answer **how to create** a class. It only enters part of the question. With a little bit more text i'm sure you can answer the full question. Also I think it is not nice to have to register the classes. And finally you said nothing about static vs virtual constructors. Oh and what about constructors with and without parameters? – Johan Apr 24 '11 at 22:20
  • 8
    @Johan: you *can't* create a class at runtime... if you like to be picky, you can create class *instances*, not classes. Also whatever technique you would like to use to get a class reference to create an instance you need some sort of mapping between class referneces and whatever identifier you'd like to use to retrieve them. Unless needing to work in a very generic way, IMHO RTTI is the best way to make code undreadable (and slow). But RTTI too "registers" names to the corresponding metadata and code. Anyway, instead of complaining about other's answers, offer your own... –  Apr 24 '11 at 23:11
  • Didn't mean to get you upset, you asked why the downvote, I answered. And I'm not confident enough on this subject to put in my own answer, it's just that your answer is... incomplete. BTW Idsandon why are YOU getting upset? – Johan Apr 24 '11 at 23:23
  • @ldsandon the principle of criticising without offering your own answer is perfectly valid. – David Heffernan Apr 25 '11 at 08:28
  • @Johan, everyone knows how to create an object (knowing which class in compile time). What has ben asked how to create an object which class you know only in runtime. So, all you need is how get the class to be able to call `Class.Create` or `Class.Create([parameters])`. – Fabricio Araujo Apr 25 '11 at 16:56
  • @Fabricio, the answer whilst being a really nice piece of knowledge, does not specify how to create a class *instance*. And does not go into the other points I pointed out above. Of course I always meant class instance, I would think that was obvious given the context. *--Don't listen to what I say, listen to what I mean, Richard Feynman*. – Johan Apr 25 '11 at 17:06
12

The normal way to do this is with virtual constructors. A good example is TComponent which you are no doubt familiar.

TComponent has the following constructor:

constructor Create(AOwner: TComponent); virtual;

The other key to this is TComponentClass which is declared as class of TComponent.

When the VCL streams .dfm files it reads the name of the class from the .dfm file and, by some process that we don't need to cover here, converts that name into a variable, ComponentClass say of type TComponentClass. It can then instantiate the object with:

Component := ComponentClass.Create(Owner);

This is the big advantage of having a virtual constructor and I would encourage you to take the same approach.

If you have to use a string to identify the class then you'll still need to come up with a lookup routine to convert from the string class name to a class reference. You could, if convenient, hook into the same VCL mechanism that TComponent uses, namely RegisterClass.

Alternatively if you could replace name in your code with a class reference then you could write:

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;
Andriy M
  • 76,112
  • 17
  • 94
  • 154
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • +1 This is my preferred method even though you have to code your own lookup (a simple TStringList with the class references in the Objects is usually more than adequate) because it avoids the bloating of the RTTI and the requirement of specific class as the root of your own class hierarchy. – Marjan Venema Apr 25 '11 at 07:45
  • @marjan you can use one of the system registeries but doing so introduces concerns over namespaces. The most important thing is to know about virtual constructors which is quite a nuance and one which neither of the other answers mention. – David Heffernan Apr 25 '11 at 07:52
  • 1
    yes, virtual constructors are indeed the crux when using class references. Virtual constructors are so "natural" to me that I forget that not all languages have them and/or that not all programmers are familiar with them. Thanks for drawing my attention to that. It will help in my coaching role at work. – Marjan Venema Apr 25 '11 at 08:22
  • I had used it in an application to reduce circular references from main form to new forms (since I like to do MDI GUIs). I only had main form to reference an auxiliar form unit in uses clause when it really needed such. Made it so much smaller and clear.... ;-) – Fabricio Araujo Apr 25 '11 at 17:52
  • +1 Nice clean answer. Suggest that the type of `result` is demonstrated in the example. For example, if `TFoo` had a base class of (say) `TForm` as in `TFoo = class(TForm)`, declare as `result: TForm;`. – AlainD Apr 12 '20 at 10:37