Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass a TComparer<T> class to a generic type in Delphi?

I want to create a generic record with some functionality, including a search function. To perform this search I wanted to pass a custom Comparer to compare items. If it were a class, I could pass it in the constructor, but I want to use a record to avoid to create or free it. So, I don't have a constructor where to initialize the Comparer.

Then I decided to pass the Comparer class like a Type parameter, and when I need the comparer, create a instance of the comparer using TComparer.Default.

Well, here is the code:

TMyArray<T,C: TComparer<T>> = record
  FComparer: IComparer<T>;
  Items: TArray<T>;

  function Contains<AItem: T>: Boolean;
end;

The problem appears when I try to use it in this way:

TMyRecord = record
  Score: Real;
  A: string;
  B: string;
end;

TMyRecordComparer = class(TComparer<TMyRecord>)
  function Compare(Left, Right: TMyRecord): Integer;
end;

TMyRecordArray = TMyArray<TMyRecord, TMyRecordComparer>;  

With this last declaration, I get this error: E2515 Type parameter 'T' is not compatible with type 'System.Generics.Defaults.TComparer\'.

Any idea how to solve this issue?

like image 207
Leon Avatar asked May 29 '19 08:05

Leon


2 Answers

While David's answer fixes the problem with the compile error, I would actually use a different constraint:

TMyArray<T; C: class, constructor, IComparer<T>> = record

That means C must be a class with a parameterless constructor and implement the IComparer<T> interface. That relaxes the constraint a little, as you don't have to inherit your comparer from System.Generics.Collections.TComparer<T> but only implement the needed IComparer<T> interface.

Also, fwiw, with the code posted in your question you will get a W1010 warning which means you are missing an override on your TMyRecordComparer.Compare method (and its missing const). This is needed if you inherit from TComparer<T> as that one implements IComparer<T> with a virtual abstract method.

Your idea to use .Default will also not work as that one creates a default implementation for a comparer for type T. But you want to use the custom one you specified.

So with the constraint I wrote above you then can do something like this (naive non-threadsafe implementation):

function TMyArray<T, C>.Contains<AItem>: Boolean;
begin
  if FComparer = nil then
    FComparer := C.Create;
  // ....
end;

And last but not least, I am not sure your Contains method is correct - the way you wrote it suggests that you want to check if your array contains an element of type AItem that is of type T or a sub type (or if T is an interface type, implements T) - I would guess you meant to write

function Contains(AItem: T): Boolean;
like image 78
Stefan Glienke Avatar answered Nov 08 '22 02:11

Stefan Glienke


The problem is the syntax of your constraint:

TMyArray<T,C: TComparer<T>>

This constrains both T and C to be TComparer<T>.

Instead you need this:

TMyArray<T; C: TComparer<T>>

The documentation gives examples of the various syntax options.

like image 30
David Heffernan Avatar answered Nov 08 '22 01:11

David Heffernan