How do I use a generic method in a derived type
Asked Answered
G

2

7

This would seem fairly simple and maybe I am just missing a bit of syntax glue... Here is my simple generic (Delphi XE3) example:

unit Unit1;

interface

uses
  generics.collections;

type

X = class
public
  Id: Integer;
end;

XList<T : X> = class( TObjectList<T> )
 function Find(Id: Integer) : T;
end;

Y = class(X)

end;

YList = class(XList<Y>)
end;

implementation

{ XList<T> }

function XList<T>.Find(Id: Integer): T;
var
  t: X;
begin
  for t in Self do
    if t.Id = Id then
      Result := t;
end;

end.

This will not compile with "[dcc32 Error] Unit1.pas(41): E2010 Incompatible types: 'Y' and 'X'". It is down to the line:

YList = class(XList<Y>)
end;

Y derives from X so why is there an issue?

Gliadin answered 22/10, 2013 at 10:41 Comment(0)
N
6

I had to reimplement the Find method as follows to fix it:

{ XList<T> }

function XList<T>.Find(Id: Integer): T;
var
  item: T;
begin
  for item in Self do
    if item.Id = Id then
      Exit(item);
  Result := nil;
end;

What´s important here is to replace the type used in the variable declaration from X to T.

Then I just renamed the variable from t to item to avoid a name collision with the type placeholder T and raplaced the Result := item by Exit(item) to return the found item and quit the method.

Noyade answered 22/10, 2013 at 11:17 Comment(6)
Great stuff @AlexSC. I originally had 'var t: T'. I had forgotten about case insensitivity. My C# generics stuff got in the way there!Gliadin
As an aside ... why doesn't the original work? T is derived from X.Gliadin
@Rob: sorry, I don´t have an answer for that. For me the compiler located the error message in the last line of the unit, what suggests me that it has something to do with the generic type instantiation. It seems that the compiler is very strict about type matching when it comes to generics.Noyade
@Rob, read David's answer if you want to know why : https://mcmap.net/q/866164/-delphi-generics-and-polymorphismDrip
@Drip It's actually rather more prosaic than generic variance. I've added an answer to explain.Nemhauser
@Noyade The compiler is no more strict for generics than for non-generic code. The non-generic version of the code would also fail to compile.Nemhauser
N
8

Alex's answer is the correct solution to the problem. And it also does well to return from the function as soon as the answer is known.

I'd like to expand on the answer with some more explanation. In particular I'd like to answer the question you asked in comments to Alex's answer:

As an aside ... why doesn't the original work? T is derived from X.

The problem code is here:

function XList<T>.Find(Id: Integer): T;
var
  t: X;
begin
  for t in Self do
    if t.Id = Id then
      Result := t;
end;

The way to think about generics, is to imagine what the code looks like when you instantiate the type and supply a concrete type parameter. In this case, let's substitute T with Y. Then the code looks like this:

function XList_Y.Find(Id: Integer): Y;
var
  t: X;
begin
  for t in Self do
    if t.Id = Id then
      Result := t;
end;

Now you have a problem at the line that assigns to Result:

Result := t;

Well, Result is of type Y, but t is of type X. The relationship between X and Y is that Y is derived from X. So an instance of Y is an X. But an instance of X is not a Y. And so the assignment is not valid.

As Alex correctly pointed out, you need to declare the loop variable to be of type T. Personally I would write the code like this:

function XList<T>.Find(Id: Integer): T;
begin
  for Result in Self do
    if Result.Id = Id then
      exit;
  Result := nil;
  // or perhaps you wish to raise an exception if the item cannot be found
end;

This also deals with the problem that your search routine leaves its return value uninitialized in case the item is not found. That's a problem that you compiler would have warned about once you got the code as far as actually compiling. I do hope you enable compiler warnings, and deal with them when they appear!

Nemhauser answered 22/10, 2013 at 12:2 Comment(0)
N
6

I had to reimplement the Find method as follows to fix it:

{ XList<T> }

function XList<T>.Find(Id: Integer): T;
var
  item: T;
begin
  for item in Self do
    if item.Id = Id then
      Exit(item);
  Result := nil;
end;

What´s important here is to replace the type used in the variable declaration from X to T.

Then I just renamed the variable from t to item to avoid a name collision with the type placeholder T and raplaced the Result := item by Exit(item) to return the found item and quit the method.

Noyade answered 22/10, 2013 at 11:17 Comment(6)
Great stuff @AlexSC. I originally had 'var t: T'. I had forgotten about case insensitivity. My C# generics stuff got in the way there!Gliadin
As an aside ... why doesn't the original work? T is derived from X.Gliadin
@Rob: sorry, I don´t have an answer for that. For me the compiler located the error message in the last line of the unit, what suggests me that it has something to do with the generic type instantiation. It seems that the compiler is very strict about type matching when it comes to generics.Noyade
@Rob, read David's answer if you want to know why : https://mcmap.net/q/866164/-delphi-generics-and-polymorphismDrip
@Drip It's actually rather more prosaic than generic variance. I've added an answer to explain.Nemhauser
@Noyade The compiler is no more strict for generics than for non-generic code. The non-generic version of the code would also fail to compile.Nemhauser

© 2022 - 2024 — McMap. All rights reserved.