Search code examples
genericsdelphi-10-seattle

"as" and "is" are not applicable to generic type


I am surprised that generics are not subject to "is" and "as" operations.

How to implement following?:

function TDisplayConfManager._getDisplay<T>(parentCtl: TWinControl; indicatorName: string): T;
var
  i: Integer;
  ind: T;
begin
  for i := 0 to parentCtl.ControlCount-1 do
    if parentCtl.Controls[i] is T then begin // E2015 Operator not applicable to this operand type
      ind := parentCtl.Controls[i] as T; // E2015 Operator not applicable to this operand type
      if SameText(ind.Caption, indicatorName) then Exit(ind); // E2003 Undeclared identifier: 'Caption'
    end;
  Result := T.Create(_owner); // E2003 Undeclared identifier: 'Create'
end;

If I use base display class for all my displays, two errors go away, but I can't still use "is" and create an object:

function TDisplayConfManager._getDisplay<T>(parentCtl: TWinControl; indicatorName: string): T;
var
  i: Integer;
  ind: TBaseDisplayType;
begin
  for i := 0 to parentCtl.ControlCount-1 do
    if parentCtl.Controls[i] is T then begin // E2015 Operator not applicable to this operand type
      ind := parentCtl.Controls[i] as TBaseDisplayType;
      if SameText(ind.Caption, indicatorName) then Exit(ind);
    end;
  Result := T.Create(_owner); // E2003 Undeclared identifier: 'Create'
end;

Solution

  • The reason is that T doesn't need to be a class type; it could also be a non-class type, like an integer, a string, a record, or an array.

    So the following doesn't compile:

    type
      TTest<T> = record
        function Test(AObject: TObject): Boolean;
      end;
    
    { TTest<T> }
    
    function TTest<T>.Test(AObject: TObject): Boolean;
    begin
      Result := AObject is T; // E2015 Operator not applicable to this operand type
    end;
    

    However, if you know that your T type will always be a class type, you can express that in Delphi:

    type
      TTest<T: class> = record
        function Test(AObject: TObject): Boolean;
      end;
    
    { TTest<T> }
    
    function TTest<T>.Test(AObject: TObject): Boolean;
    begin
      Result := AObject is T;
    end;
    

    The documentation contains more details about generic constraints.

    Update in response to comment:

    You can also have constraints at the member level:

    type
      TTest = record
        function Test<T: class>(AObject: TObject): Boolean;
      end;
    
    { TTest }
    
    function TTest.Test<T>(AObject: TObject): Boolean;
    begin
      Result := AObject is T;
    end;