Search code examples
delphidelphi-10.1-berlin

Generics without parameterless constructors


Can someone explain why in the code below, class1List does not require class1 to have a parameterless constructor, but class2list does require class 2 to have a parameterless constructor.

unit Unit11;

interface

uses
  System.Generics.Collections;

type
  class1 = class
  public
    constructor Create( const i : integer ); virtual;
  end;

  class1List<T : class1 > = class( TObjectList< T > )
  public
    function AddChild( const i : integer ) : T;
  end;

  class2 = class
  public
    constructor Create( const i : integer );
  end;

  class2List<T : class2 > = class( TObjectList< T > )
  public
    function AddChild( const i : integer ) : T;
  end;


implementation

{ class1List<T> }

function class1List<T>.AddChild(const i: integer): T;
begin
  Result := T.Create( i );
  inherited Add( Result );
end;

{ class2List<T> }

function class2List<T>.AddChild(const i: integer): T;
begin
  Result := T.Create( i );
  inherited Add( Result );
end;

{ class1 }

constructor class1.Create(const i: integer);
begin

end;

{ class2 }

constructor class2.Create(const i: integer);
begin

end;

end.

Solution

  • function class1List<T>.AddChild(const i: integer): T;
    begin
      Result := T.Create( i );
      inherited Add( Result );
    end;
    

    The constructor of class1 is declared virtual. Therefore the compiler knows that T.Create yields an instance of T whose intended constructor has been called. Hence the compiler accepts this code. Note that earlier versions of the compiler would reject this code and force you to use the following cast

    Result := T(class1(T).Create( i ));
    

    But more recent versions of the compiler have removed the need for such trickery.


    function class2List<T>.AddChild(const i: integer): T;
    begin
      Result := T.Create( i );
      inherited Add( Result );
    end;
    

    The constructor of class2 is not virtual and so the compiler knows that were it to call the constructor of class2, likely the class would not be properly initialised. It is prepared to call a parameterless constructor from the specialised type T if one exists, and you apply the constructor constraint when you declare the generic type. However, the language offers no way to apply a constructor constraint for constructors that accept parameters.

    Now, you could apply the constructor constraint, but that would do no good. In order for the instance to be initialised properly, you need to call the constructor with the parameter. Which means, in practical terms, that you should use the first approach using a virtual constructor.

    Don't be tempted to cast your way out of this hole. This code will compile

    Result := T(class2(T).Create( i ));
    

    but will likely not do what you want. This will call the static constructor of class2 which is surely not what you want.