Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi interface generic function - Is there a work around?

I have the following code

IProxy<T> = interface
  ['{1E3A98C5-78BA-4D65-A4BA-B6992B8B4783}']
  function Setup : ISetup<T>;
  function Proxy : T;
  function CastAs<I : IInterface> : IInterface;
end;

Is there a way to work around the compiler error that is received when compiling?

"[DCC Error] Delphi.Mocks.pas(123): E2535 Interface methods must not have parameterized methods"

Basically I would like to have this interface be passed around and be able to cast off it, by passing in the type to cast to and having that type returned. I can achieve this with a class, however would prefer passing around an interface.

Additional Info:

Say I have the following class

TInterfaceProxy<T> = class(TBaseProxy<T>)
private type
  TProxyVirtualInterface = class(TVirtualInterface)
  private
    FProxy : TInterfaceProxy<T>;
  protected
  public
    function QueryInterface(const IID: TGUID; out Obj): HRESULT; override; stdcall;
    constructor Create(AProxy : TInterfaceProxy<T>; AInterface: Pointer; InvokeEvent: TVirtualInterfaceInvokeEvent);
  end;
private
  FVirtualInterfaces : TDictionary<TGUID, TProxyVirtualInterface>;
protected
  function InternalQueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
  function QueryInterface(const IID: TGUID; out Obj): HRESULT; override;
  function Proxy : T;override;
  function CastAs<I: IInterface> : I;
public
  constructor Create;override;
  destructor Destroy;override;
end;

CastAs works well here as the newly requested cast can be created a new virtual interface. Now if I want to pass this class around its fine. However if I require it as an interface i.e. TInterfaceProxy<T> = class(TBaseProxy<T>, IProxy<T>) it doesn't work understand that. Don't agree with it, but understand.

Therefore how do I get around this restriction so that I can call a CastAs function, pass in a type (any interface type to start with), and be able to create a virtual interface from it?

like image 702
Jason Avatar asked May 14 '13 00:05

Jason


People also ask

What are generic types used for?

Generics overview Use generic types to maximize code reuse, type safety, and performance. The most common use of generics is to create collection classes. The . NET class library contains several generic collection classes in the System.

What is Delphi interface?

In Delphi, "interface" has two distinct meanings. In OOP jargon, you can think of an interface as a class with no implementation. In Delphi unit definition interface section is used to declare any public sections of code that appear in a unit. This article will explain interfaces from an OOP perspective.


3 Answers

Something like the following compiles for me with Delphi 11.0 (yet to see if it crashes):

TStoryItemList = TList<IStoryItem>;

TObjectListEx<T: class> = class
    class function GetAllOfInterface<AInterface: IInterface>(const list: TList<T>): TList<AInterface>;
  end;

//...

class function TObjectListEx<T>.GetAllOfInterface<AInterface>(const list: TList<T>): TList<AInterface>;
var
  itemAsAInterface: AInterface;
begin
  var guid := TRttiInterfaceType(TRttiContext.Create.GetType(TypeInfo(AInterface))).GUID;
  var listOfAInterface := TList<AInterface>.Create;
  for var item in list do
    if Supports(item, guid, itemAsAInterface) then
      listOfAInterface.Add(itemAsAInterface);
  result := listOfAInterface;
end;

usage

function TStoryItem.GetStoryItems: TStoryItemList;
begin
  result := TObjectListEx<TControl>.GetAllOfInterface<IStoryItem>(Controls);
end;
like image 196
George Birbilis Avatar answered Nov 05 '22 05:11

George Birbilis


Interfaces do not support generic parameterized methods, as the compiler says.

There is no workaround because it's a fundamental limitation. Parameterized methods in classes are implemented by adding one method per instantiation to the class. That works for classes since they are concrete, but is not viable for interfaces. That's because interfaces are a table of functions and the size of that table cannot vary depending on which generic method instantiations happen to be present elsewhere in the code. For similar reasons, generic methods cannot be virtual or dynamic.

The code in your question is a little mis-leading also. You wrote:

function CastAs<I : IInterface> : IInterface;

but I'm sure you meant:

function CastAs<I : IInterface> : I;

In any case, it's not possible. One option is to use a class instead. I agree that this is a bind.

If you want to do it in an interface, the best you can do is:

function CastAs(const IID: TGUID): IInterface;

But you'd have to call it like this:

MyIntf := ProxyIntf.CastAs(IMyIntf) as IMyIntf;

which feels somewhat foul.

Choose your poison!

like image 41
David Heffernan Avatar answered Nov 05 '22 05:11

David Heffernan


As the error message states, a method in an interface cannot have Generic parameters. The compiler simply does not support it, and this is documented as such:

http://docwiki.embarcadero.com/RADStudio/XE4/en/Overview_of_Generics

Parameterized method in interface

A parameterized method (method declared with type parameters) cannot be declared in an interface.

In other words, your CastAs method is illegal because it is declared in an interface type. On the other hand, you don't need such a method in the first place. You can use SysUtils.Supports() instead for casting one interface to another.

like image 7
Remy Lebeau Avatar answered Nov 05 '22 05:11

Remy Lebeau