Delphi - Interface inheritance with generics

2019-02-17 03:56发布

问题:

I am currently stuck with a compiling error, no one in our company can help and I am sadly not finding the correct search patterns for SO or google.

As code I am using 2 Interfaces, inherited and 2 Classes, inherited. The following code reproduces the error:

program Project22;

{$APPTYPE CONSOLE}
type
  IStorageObject = interface(IInterface)
  end;
  TObjectStorage<T: IStorageObject> = class(TObject)
  end;
  IKeyStorageObject<TKey> = interface(IStorageObject)
  end;
  TKeyObjectStorage<TKey, T: IKeyStorageObject<TKey>> = class(TObjectStorage<T>)
  end;
  TImplementingClass<TKey> = class(TInterfacedObject, IKeyStorageObject<TKey>)
  end;
begin
  TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.

The compiler error for 'TKeyObjectStorage' is:

[DCC Error] Project22.dpr(11): E2514 Type parameter 'T' must support interface 'IStorageObject'

What I think is, that the compiler is not recognizing that Parameter T of the Class 'TKeyObjectStorage' correctly. It should be correct, since the wanted Type 'IKeyStorageObject' has the parent type IStorageObject.

Why is this not working? What am I doing wrong? Is this not possible in Delphi?

回答1:

Update

The original question had a problem which I identified (see below). However, the fix I describe there is fine for XE3 and later, but that program below does not compile in XE2. Thus I conclude that this is an XE2 generics compiler bug.

Anyway, here's a workaround for Delphi XE2:

{$APPTYPE CONSOLE}
type
  IStorageObject = interface(IInterface)
  end;
  TObjectStorage<T: IStorageObject> = class(TObject)
  end;
  IKeyStorageObject<TKey> = interface(IStorageObject)
  end;
  TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>, IStorageObject> = class(TObjectStorage<T>)
  end;
  TImplementingClass<TKey> = class(TInterfacedObject, IStorageObject, IKeyStorageObject<TKey>)
  end;
begin
  TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.

Original answer

It would have been better if you had provided a complete program that exhibited the compiler error. You need to attempt to instantiate an object to see that error.

But, I think I've reproduced your problem. So I believe that the issue is that this code:

TKeyObjectStorage<TKey, T: IKeyStorageObject<TKey>> = ...

applies the generic constraint to both TKey and T. Now, clearly you only want the constraint to apply to T so you'll need to write:

TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>> = ...

Here's a short program that compiles following the change in Delphi XE3:

{$APPTYPE CONSOLE}
type
  IStorageObject = interface(IInterface)
  end;
  TObjectStorage<T: IStorageObject> = class(TObject)
  end;
  IKeyStorageObject<TKey> = interface(IStorageObject)
  end;
  TKeyObjectStorage<TKey; T: IKeyStorageObject<TKey>> = class(TObjectStorage<T>)
  end;
  TImplementingClass<TKey> = class(TInterfacedObject, IKeyStorageObject<TKey>)
  end;
begin
  TKeyObjectStorage<Integer, TImplementingClass<Integer>>.Create;
end.

This is quite a nuance, the changing of a comma to a semi-colon. Programming by significant punctuation is never much fun. That said, you are familiar with the difference between commas and semi-colons in formal parameter lists and so it should not come as too much of a surprise to see the same distinction drawn here.

The documentation does cover this mind you:

Multiple Type Parameters

When you specify constraints, you separate multiple type parameters by semicolons, as you do with a parameter list declaration:

type
  TFoo<T: ISerializable; V: IComparable>

Like parameter declarations, multiple type parameters can be grouped together in a comma list to bind to the same constraints:

type
  TFoo<S, U: ISerializable> ...

In the example above, S and U are both bound to the ISerializable constraint.