Implementing generic interfaces in Delphi

Delphi supports generic interfaces; for example we can declare a generic interface

type
  IChecker<T> = interface
    function Check(const Instance: T): Boolean;
  end;

and use this generic interface as follows:

unit UseDemo;

interface

uses GenChecks;

type
  TDemo<T> = class
  private
    FChecker: IChecker<T>;
  public
    constructor Create(AChecker: IChecker<T>);
    procedure Check(AValue: T);
  end;

implementation

{ TDemo<T> }

procedure TDemo<T>.Check(AValue: T);
begin
  if FChecker.Check(AValue)
    then Writeln('Passed')
    else Writeln('Stopped')
end;

constructor TDemo<T>.Create(AChecker: IChecker<T>);
begin
  FChecker:= AChecker;
end;

end.

To implement the above generic interface IChecker we need a generic class; the straightforward solution is

type
  TChecker<T> = class(TInterfacedObject, IChecker<T>)
    function Check(const Instance: T): Boolean;
  end;

If the IChecker interface can be implemented like that, we need nothing else. The problem with the above implementation is that we are limited to the generic constraints on the type T and canโ€™t use properties of specific types like Integer or string that will finally be substituted for the type T.

A more elastic solution is to introduce an abstract base type and derive the specific implementations from it. Here is a full code example:

program GenericEx1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  GenChecks in 'GenChecks.pas',
  UseDemo in 'UseDemo.pas';

procedure TestInt;
var
  Demo: TDemo<Integer>;

begin
  Demo:= TDemo<Integer>.Create(TIntChecker.Create(42));
  Demo.Check(0);
  Demo.Check(42);
end;

procedure TestStr;
var
  Demo: TDemo<string>;

begin
  Demo:= TDemo<string>.Create(TStrChecker.Create('trololo'));
  Demo.Check('ololo');
  Demo.Check('olololo');
end;

begin
  TestInt;
  TestStr;
  ReadLn;
end.
unit GenChecks;

interface

type
  IChecker<T> = interface
    function Check(const Instance: T): Boolean;
  end;

type
  TCustomChecker<T> = class(TInterfacedObject, IChecker<T>)
  protected
    FCheckValue: T;
    function Check(const Instance: T): Boolean; virtual; abstract;
  public
    constructor Create(ACheckValue: T);
  end;

  TIntChecker = class(TCustomChecker<Integer>)
  protected
    function Check(const Instance: Integer): Boolean; override;
  end;

  TStrChecker = class(TCustomChecker<string>)
  protected
    function Check(const Instance: string): Boolean; override;
  end;

implementation

{ TCustomChecker<T> }

constructor TCustomChecker<T>.Create(ACheckValue: T);
begin
  FCheckValue:= ACheckValue;
end;

{ TIntChecker }

function TIntChecker.Check(const Instance: Integer): Boolean;
begin
  Result:= Instance = FCheckValue;
end;

{ TStrChecker }

function TStrChecker.Check(const Instance: string): Boolean;
begin
  Result:= Length(Instance) = Length(FCheckValue);
end;

end.

In the above example each interface reference ICheck references its own class instance; this is necessary because every instance contains a parameter (FCheckValue) set in the constructor. If an implementation does not require such a parameter creating new instances for every interface reference will be an overhead. A better solution is to use a singleton instance.

Here is a full code example for the integer type:

program GenericEx2;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  GenChecks in 'GenChecks.pas',
  UseDemo in 'UseDemo.pas';

procedure TestInt;
var
  Demo: TDemo<Integer>;

begin
  Demo:= TDemo<Integer>.Create(TIntChecker.Ordinal);
  Demo.Check(0);
  Demo.Check(42);
end;

begin
  TestInt;
  ReadLn;
end.
unit GenChecks;

interface

uses Generics.Defaults;

type
  IChecker<T> = interface
    function Check(const Instance: T): Boolean;
  end;

  TCustomChecker<T> = class(TSingletonImplementation, IChecker<T>)
  protected
    function Check(const Instance: T): Boolean; virtual; abstract;
  end;

  TIntChecker = class(TCustomChecker<Integer>)
  private
    class var
      FOrdinal: TCustomChecker<Integer>;
  public
    class function Ordinal: TIntChecker;
  end;

implementation

type
  TOrdinalIntChecker = class(TIntChecker)
  public
    function Check(const Instance: Integer): Boolean; override;
  end;

{ TOrdinalIntChecker }

function TOrdinalIntChecker.Check(const Instance: Integer): Boolean;
begin
  Result:= Instance = 42;
end;

{ TIntChecker }

class function TIntChecker.Ordinal: TIntChecker;
begin
  if FOrdinal = nil then
    FOrdinal := TOrdinalIntChecker.Create;
  Result := TIntChecker(FOrdinal);
end;

end.
Advertisements

9 thoughts on “Implementing generic interfaces in Delphi

  1. I’m dead honest, I don’t understand anything. Yes, you can have generic Interfaces. No, the compiler is not smart enough to give every Interface a GUID, although it could. This way, you can’t check at runtime, if some instance supports IChecker.
    Maybe there’s something with my head, but even after reading it three times, I don’t even see a PROBLEM you’re describing and working around. Can you use simple words? Maybe with lots of vowels.

    • Well the PROBLEM I am currently working on is to use BigInteger type as a key for the generic TDictionary type from the Generics.Collections unit, and I need to implement generic IEqualityComparer interface.
      This not a common problem. I’ve written this article to understand better what I should do and how it should be done.

      • I understand what you’re trying to do (this explanation helped a lot), but it seems the biggest problem is the constraints on Delphi generics ; as you put it “…we are limited to the generic constraints on the type T and canโ€™t use properties of specific types like Integer or string that will finally be substituted for the type T.” That rather defeats the purposes of generics and really makes me wonder if it’s true to say that Delphi has generics in the full sense of the term. Delphi has a limited version of generics is more accurate. ๐Ÿ˜ฆ

        On an even bigger level of problems ๐Ÿ™‚ this all stems from static typing. Vast amounts of complicated boilerplate code and infrastructure often need to be written to get around the handcuffs of static typing that we put on to avoid errors that in real life almost never happen and when they do are the easiest errors of all to detect. ๐Ÿ˜ฆ You know your code will work but the compiler doesn’t hence you’re finding yourself going through lots of extra work to please the machine; it’s really in charge. ๐Ÿ˜ฆ In a language with duck typing you’d simply need to have methods named Equals and GetHashCode (or the equivalent in that language) and that’s it. Heck, in Python bigints are the default integers and you wouldn’t even need to be writing your code in the first place! ๐Ÿ˜‰

    • I’ll never understand this “GUID” nonsense. The compiler should just check the methods of the object in question. Classic duck typing.

      • The reason is: Delphi is a strongly typed language, period. Would it be nice to have dynamic typing? Of course but let’s not forget that it’s not, hence no duck typing. Of course they could implement something similar as they did for the for-in loop (enumerable thing needs GetEnumerator which returns something that has Current and MoveNext) but then they would need to implement such things all over the place for various cases.

      • @sglienke:

        I don’t think this has anything to do with static or dynamic typing. It’s just about checking that the object has methods named X, Y and Z that match the signature. Other statically typed languages don’t require a GUID. That was a kludge that involved COM.

        Here’s D’s documentation on interfaces, including COM interfaces: no GUID present:

        http://dlang.org/interface.html

        >Of course they could implement something similar as they did for the for-in loop (enumerable thing
        >needs GetEnumerator which returns something that has Current and MoveNext)

        That’s the norm for languages nowadays.

        > but then they would need to implement such things all over the place for various cases.

        That’s what they get paid the (not so) big bucks for. ๐Ÿ™‚ Seriously, a language design shouldn’t be compromised to make things easy for the implementer; it’s supposed to make things easier for the developer using it.

        I wish they would implement such things all over the place for various cases. It would make the language both cleaner and more powerful, iterators could be used everywhere and thus truly be useful, etc.

      • @Joseph:

        I don’t see your point. Delphi Interfaces don’t need a GUID. In fact a GUID is only needed when you want to either check if an objects implements such interface or if you have interface A and want to check if the implementing class also implements interface B (Supports). Both things I cannot see in the documentation you are mentioning. All code examples are using a concrete class that implements one or two interfaces and assign an object reference of this class to one of the interfaces which works exactly like that in Delphi also (the only crux is the reference counting that kicks in and bites you when you are mixing objects and interfaces). Also there is no mentioning of being able to assign an object that does not implement interface A to a variable of interface A if the object implements all methods that interface A has which you are wishing for (which you can do in Delphi with the help of some RTTI – see my blog post about duck typing).

    • It’s simple. If you are declaring a generic interface you don’t have any specializations of that interfaces yet. So if module 1 specializes this interface with TFoo and module 2 also specializes the interface with TFoo how to make sure the same GUID gets created? Sure want could use some hashing algo for the generic parameter type to generate the same GUID but it’s not as simple. Of course you can do wrong things because Supports returns true for different specializations but that can be solved otherwise (see Spring4D collections for example that provide a property that returns the generic parameter type which can be checked for in addition).

  2. I have done something similar in the past. Instead of creating an specific class for each possible type, I am just converting T to a Variant (using TValue). Then I can get the varType and do whatever I need to do.
    This approach doesn’t work with objects, but works like a charm when dealing with base types.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s