Warning: filemtime(): stat failed for /home/developpez/www/developpez-com/upload/sjrdhttp://sjrd.developpez.com/stylesheet.css in /home/developpez/www/developpez-com/template/entete.php on line 405
IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Generics with Delphi 2009 Win32

With, as a bonus, anonymous routines and routine references

Date de publication : November 13th, 2008


VIII. Generic methods
VIII-A. A generic Min function
VIII-B. Overloading and constraints
VIII-C. Some extras for TList<T>


VIII. Generic methods

We have explored so far the many possibilities offered by generics, applied to types defined by the developer. It is time to move on to generic methods.

In many presentations of generics and templates in other languages, this use of generics is explained first. Again, I have preferred to begin with generic types, which are used more often.


VIII-A. A generic Min function

In order to introduce the concept, we will write a class method TArrayUtils.Min<T>, which finds and returns the smallest element of an array. We will then, of course, need a comparer of type IComparer<T>.

Like a generic type name, a generic method name must be followed by the generic parameters, between angle brackets. Here, the generic type is the type of the elements of the array.

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; static;
  end;
				
warning No, it is not possible to declare a global routine with generic parameters. A possible reason for this limitation is due to a parallelism with the Delphi.NET syntax, in order to reduce development and maintenance efforts, internally.
idea To make up for this limitation, we then use class methods. Better declared as static. This has nothing to do with the homonym keyword in C++. It has to do here with static linking. This is to say, in such a method, there is no Self, and then calls to virtual class methods, or to virtual constructors, are to "virtualised". In other words, it is like they were not virtual any more.
Finally, it results in a genuine global routine, but with a different namespace.
info Unlike some other languages, it is not necessary for a parameter at least to use the generic type. Thus, it is possible to write a method Dummy<T>(Int: Integer): Integer, which has no formal paremeter whose type depends on the generic parameter T. In C++, for example, that would not get pass compilation.
The implementation of the method is quite similar to classes. You have to repeat the angle brackets with the generic types, but not their possible constraints. Our Min method will then look like this:

class function TArrayUtils.Min<T>(const Items: array of T;
  const Comparer: IComparer<T>): T;
var
  I: Integer;
begin
  if Length(Items) = 0 then
    raise Exception.Create('No items in the array');

  Result := Items[Low(Items)];
  for I := Low(Items)+1 to High(Items) do
    if Comparer.Compare(Items[I], Result) < 0 then
      Result := Items[I];
end;
				
Nothing exceptional, as you may see ;-)


VIII-B. Overloading and constraints

Let us take advantage of this nice example to review our constraints, and provide an overloaded version for those element types which support the IComparable<T> interface (this interface is declared in System.pas).

Before that, let us add another overload which takes a routine reference of type TComparison<T>. Remember we can easily "convert" a TComparison<T> call-back to a IComparer<T> interface with the TComparer<T>.Construct method.

You may observe the usage of the method CompareTo on the Left parameter. This call is of course permitted only because of the constraint.

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;
  end;

class function TArrayUtils.Min<T>(const Items: array of T;
  const Comparison: TComparison<T>): T;
begin
  Result := Min<T>(Items, TComparer<T>.Construct(Comparison));
end;

class function TArrayUtils.Min<T>(const Items: array of T): T;
var
  Comparison: TComparison<T>;
begin
  Comparison :=
    function(const Left, Right: T): Integer
    begin
      Result := Left.CompareTo(Right);
    end;
  Result := Min<T>(Items, Comparison);
end;
				
info Have a look at the call to Min<T>: specifying the actual type between angle brackets is mandatory even there. This is not the case in other languages like C++.
Now, we want to provide a fourth overloaded method, again only with the Items parameter, but with an unconstrained T parameter. This version would use TComparer<T>.Default.

But this is impossible! Indeed, even though the constraints on types do change, the parameters (arguments) are still the same. So, the two overloads are totally ambiguous! Therefore, the following declaration would result in a compiler error:

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;
    class function Min<T>(
      const Items: array of T): T; overload; static; // Compiler error
  end;
				
Then, we must choice: abandon either the first or the second, or else use another name. And as, until base types like Integer support the IComparable<T> interface, you will need both as often, you will have to opt for another name ;-)

type
  TArrayUtils = class
  public
    class function Min<T>(const Items: array of T;
      const Comparer: IComparer<T>): T; overload; static;
    class function Min<T>(const Items: array of T;
      const Comparison: TComparison<T>): T; overload; static;
    class function Min<T: IComparable<T>>(
      const Items: array of T): T; overload; static;

    class function MinDefault<T>(
      const Items: array of T): T; static;
  end;

class function TArrayUtils.MinDefault<T>(const Items: array of T): T;
begin
  Result := Min<T>(Items, IComparer<T>(TComparer<T>.Default));
end;
				
Why did I cast explicitly Default into an IComparer<T>, though it is obviously already an IComparer<T>? Because routine references and overloads still do not work very well with each other, and the compiler seems to get confused with the mixing. Without the cast, the compilation fails ...


VIII-C. Some extras for TList<T>

Even though the TList<T> is quite a good innovation, it could nevertheless provide some more practical methods.

Here is, for example, an implementation of the .NET FindAll method for TList<T>. This method aims to select a sub-list from a predicate function. What we call a predicate function is a call-back routine that takes an element of the list, and returns True if it should be selected, False otherwise. We define a routine reference type TPredicate<T> as follows:

unit Generics.CollectionsEx;

interface

uses
  Generics.Collections;

type
  TPredicate<T> = reference to function(const Value: T): Boolean;
				
Then, since it is unfortunately impossible to write a class helper for a generic class, we will write a class method FindAll<T> which is going to to that. Since we are deprived of class helper, we will at least take advantage of it to be more general, working with an enumerator, or an enumerable.

type
  TListEx = class
  public
    class procedure FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>;
      const Predicate: TPredicate<T>); overload; static;
    class procedure FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>;
      const Predicate: TPredicate<T>); overload; static;
  end;

implementation

class procedure TListEx.FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>;
  const Predicate: TPredicate<T>);
begin
  while Source.MoveNext do
  begin
    if Predicate(Source.Current) then
      Dest.Add(Source.Current);
  end;
end;

class procedure TListEx.FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>;
  const Predicate: TPredicate<T>);
begin
  FindAll<T>(Source.GetEnumerator, Dest, Predicate);
end;

end.
				
One can use this method as follows:

Source := TList<Integer>.Create;
try
  Source.AddRange([2, -9, -5, 50, 4, -3, 7]);
  Dest := TList<Integer>.Create;
  try
    TListEx.FindAll<Integer>(Source, Dest, TPredicate<Integer>(
      function(const Value: Integer): Boolean
      begin
        Result := Value > 0;
      end));

    for Value in Dest do
      WriteLn(Value);
  finally
    Dest.Free;
  end;
finally
  Source.Free;
end;
				
info Again, the cast is necessary because of overloads. This is quite deplorable, but yet it is so. If you prefer not to have casts, use different names, or get rid of one of the two overloads.
It is up to you to complement this class with other methods like FindAll :-)

 

Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41

Warning: include(http://sjrd.developpez.com/references.inc): failed to open stream: no suitable wrapper could be found in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41

Warning: include(): Failed opening 'http://sjrd.developpez.com/references.inc' for inclusion (include_path='.:/opt/php56/lib/php') in /home/developpez/www/developpez-com/upload/sjrd/delphi/tutoriel/generics/index.php on line 41

Valid XHTML 1.0 TransitionalValid CSS!

Copyright © 2008-2009 Sébastien Doeraene. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.