Delphi XE: class constructor doesn't get called in a class using generics
Asked Answered
H

2

11

Consider the following example (I am using Delphi XE):

program Test;

{$APPTYPE CONSOLE}

type
  TTestClass<T> = class
  private
    class constructor CreateClass();
  public
    constructor Create();
  end;

class constructor TTestClass<T>.CreateClass();
begin
  // class constructor is not called. this line never gets executed!
  Writeln('class created');
end;

constructor TTestClass<T>.Create();
begin
  // this line, of course, is printed
  Writeln('instance created');
end;

var
  test: TTestClass<Integer>;

begin
  test := TTestClass<Integer>.Create();
  test.Free();
end.

The class constructur is never called and hence the line 'class created' is not printed. However, if I remove the generalisation and make TTestClass<T> into a standard class TTestClass, everything works as expected.

Am I missing something out with generics? Or it simply doesn't work?

Any thoughts on this would be apprechiated!

Thanks, --Stefan--

Haggerty answered 29/2, 2012 at 15:2 Comment(4)
The documentation states: "Note: The class constructor for a generic class or record may execute multiple times. The exact number of times the class constructor is executed in this case depends on the number of specialized versions of the generic type. For example, the class constructor for a specialized TList<String> class may execute multiple times in the same application." But it looks a bit like a bug then.Beezer
Yes. I read that, too. Unless "multiple times" includes zero times, this really does look like a bug.Haggerty
General rule: Don't try to make a self contained .dpr application. Always have at least one unit, and keep everything out of DPR files that you can keep out of it.Poisoning
@WarrenP - I think it's pretty clear that this was a self-contained example for the purposes of making the question easier to deal with. A fortunate coincidence that it (accidentally) preserved the conditions under which the error manifested. What's really worrying is that the generics implementation in Delphi is still so fragile as to suffer from bizarre bugs like this.Song
P
9

Looks like a compiler bug. The same code works if you move the TTestClass declaration and implementation to a separate unit.

unit TestClass;

interface
type
  TTestClass<T> = class
  private
    class constructor CreateClass();
  public
    constructor Create();
  end;

var
  test: TTestClass<Integer>;

implementation

class constructor TTestClass<T>.CreateClass();
begin
  Writeln('class created');
end;

constructor TTestClass<T>.Create();
begin
  Writeln('instance created');
end;

end.
Prioress answered 29/2, 2012 at 15:17 Comment(3)
Great, thanks for solving this! I'd +1, too if I could. Actually I had my class in it's own unit but I used it in the .dpr file only. So the answer to the question is: Class constructors of generic classes don't run for objects instantiated in the .dpr file.Haggerty
Further vindication of my resolve to avoid generics like the proverbial plague. That they still suffer from such bizarre bugs as this after this many iterations and evolutions in the compiler is truly depressing and worrying.Song
What matters is where the class is instantiated. In your example, that means where test is declared. If you moved the test declaration back to the .dpr file then the class constructor would not fire.Beezer
B
12

I can confirm that this is a bug. If the only instantiation of the class is in the .dpr file, then the class constructor does not run. If you create another unit, i.e. a separate .pas file, and instantiate a TTestClass<Integer> from there, then your class constructor will run.

I have submitted QC#103798.

Beezer answered 29/2, 2012 at 15:18 Comment(4)
Still not fixed in XE5. The worst thing is that you have to declare or instantiate a variable in the separate .pas file. And you have to do that for every T used in your program!!Isherwood
Still not fixed in XE8 @LURDBeezer
@LURD EBMT seems to force coders keep their hands off the DPR file as WarrenP mentioned.Arleen
Not fixed in D10Seattle!Isherwood
P
9

Looks like a compiler bug. The same code works if you move the TTestClass declaration and implementation to a separate unit.

unit TestClass;

interface
type
  TTestClass<T> = class
  private
    class constructor CreateClass();
  public
    constructor Create();
  end;

var
  test: TTestClass<Integer>;

implementation

class constructor TTestClass<T>.CreateClass();
begin
  Writeln('class created');
end;

constructor TTestClass<T>.Create();
begin
  Writeln('instance created');
end;

end.
Prioress answered 29/2, 2012 at 15:17 Comment(3)
Great, thanks for solving this! I'd +1, too if I could. Actually I had my class in it's own unit but I used it in the .dpr file only. So the answer to the question is: Class constructors of generic classes don't run for objects instantiated in the .dpr file.Haggerty
Further vindication of my resolve to avoid generics like the proverbial plague. That they still suffer from such bizarre bugs as this after this many iterations and evolutions in the compiler is truly depressing and worrying.Song
What matters is where the class is instantiated. In your example, that means where test is declared. If you moved the test declaration back to the .dpr file then the class constructor would not fire.Beezer

© 2022 - 2024 — McMap. All rights reserved.