The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 2,162 other followers

Interface methods are not assignment compatible with method references or methods of object.

Posted by jpluimers on 2019/04/02

Boy I wish that QC was still up and QualityPortal was publicly indexable as that would have saved me quite a bit of time tracking this down. Luckily I got help from Stefan Glienke (who maintains the awesome Spring4D library based on modern Delphi compiler support) when I mentioned

How good are you with reference to function?
I’ve an odd compiler thing throwing errors when using interfaces but not with classes.

So, for posterity:

Unlike C#, in Delphi interface methods are not compatible with method references or methods of object.

This has many manifestations, which means you can get a variety of compiler errors. I’ve listed the ones I could find below, but presume there are more and if I find more will update this post.

These are the errors you can get:

  • E2010 Incompatible types: ‘T’ and ‘Procedure’
  • E2035 Not enough actual parameters
  • E2250 There is no overloaded version of ‘FirstOrDefault’ that can be called with these arguments

These are the (now defunct, but used to be publicly accessible) QC and QualityPortal (needs sign on) entries (thanks Stefan Glienke and Blaise Thorn for reporting these):

The really frustrating part is that the RSP is marked as “new feature” whereas clearly it isn’t, so it probably never will be fixed.

A workaround for now is to wrap the interface method references with:

  • either anonymous methods (when you have just a few classes to cover, but maybe more than a few methods on the interface)
  • or instance methods on a class (when there are many classes to cover and preferably few methods on the interface)

Examples are in the code below that also shows this works fine and dandy in C#.

–jeroen

unit MethodReferencesFailOnInterfacesUnit;
interface
type
TAction<T> = reference to procedure(const Value: T);
TConstFunction<T, TResult> = reference to function(const Value: T): TResult;
TTarget = class(TInterfacedObject)
end;
IContainer<T: class> = interface(IInterface)
['{5A8A80D4-4BF9-48E9-98CB-936E7F44F43F}']
procedure Delete(const Value: T);
function FirstOrDefault(const Id: Integer): T; overload;
function FirstOrDefault(const Id: string): T; overload;
function FirstOrDefaultInteger(const Id: Integer): T;
function FirstOrDefaultString(const Id: string): T;
end;
TContainer<T: class> = class(TInterfacedObject, IContainer<T>)
procedure Delete(const Value: T);
function FirstOrDefault(const Id: Integer): T; overload;
function FirstOrDefault(const Id: string): T; overload;
function FirstOrDefaultInteger(const Id: Integer): T;
function FirstOrDefaultString(const Id: string): T;
end;
//////////////////////////////////////////////////////////////////////////////////////////////
TUsage = class
function Delete<T: class>(const Container: IContainer<T>): TAction<T>; overload;
function FirstOrDefault<T: class>(const Container: IContainer<T>): TConstFunction<Integer, T>; overload;
function FirstOrDefault<T: class>(const Container: IContainer<T>; const Id: string): TConstFunction<string, T>; overload;
function FirstOrDefaultInteger<T: class>(const Container: IContainer<T>): TConstFunction<Integer, T>;
function FirstOrDefaultString<T: class>(const Container: IContainer<T>; const Id: string): TConstFunction<string, T>;
procedure InternalAction<T: TInterfacedObject>(const Value: T; const Action: TAction<T>);
procedure InternalFunction<T: TInterfacedObject>(const Value: T; const ConstFunction: TConstFunction<Integer, T>);
procedure Run;
end;
implementation //////////////////////////////////////////////////////////////////////////////////////////////
procedure TContainer<T>.Delete(const Value: T);
begin
// NOP
end;
function TContainer<T>.FirstOrDefault(const Id: Integer): T;
begin
Result := nil;
end;
function TContainer<T>.FirstOrDefault(const Id: string): T;
begin
Result := nil;
end;
function TContainer<T>.FirstOrDefaultInteger(const Id: Integer): T;
begin
Result := nil;
end;
function TContainer<T>.FirstOrDefaultString(const Id: string): T;
begin
Result := nil;
end;
//////////////////////////////////////////////////////////////////////////////////////////////
function TUsage.Delete<T>(const Container: IContainer<T>): TAction<T>;
begin
Result := procedure(const Value: T)
begin
Container.Delete(Value);
end;
end;
function TUsage.FirstOrDefault<T>(const Container: IContainer<T>): TConstFunction<Integer, T>;
begin
Result := function(const Id: Integer): T
begin
Result := Container.FirstOrDefault(Id);
end;
end;
function TUsage.FirstOrDefault<T>(const Container: IContainer<T>; const Id: string): TConstFunction<string, T>;
begin
Result := function(const Id: string): T
begin
Result := Container.FirstOrDefault(Id);
end;
end;
function TUsage.FirstOrDefaultInteger<T>(const Container: IContainer<T>): TConstFunction<Integer, T>;
begin
Result := function(const Id: Integer): T
begin
Result := Container.FirstOrDefaultInteger(Id);
end;
end;
function TUsage.FirstOrDefaultString<T>(const Container: IContainer<T>; const Id: string): TConstFunction<string, T>;
begin
Result := function(const Id: string): T
begin
Result := Container.FirstOrDefault(Id);
end;
end;
procedure TUsage.InternalAction<T>(const Value: T; const Action: TAction<T>);
begin
// NOP
end;
procedure TUsage.InternalFunction<T>(const Value: T; const ConstFunction: TConstFunction<Integer, T>);
begin
// NOP
end;
procedure TUsage.Run;
var
LContainerInstance: TContainer<TTarget>;
LContainerInterface: IContainer<TTarget>;
LTarget: TTarget;
begin
LTarget := TTarget.Create();
LContainerInstance := TContainer<TTarget>.Create();
InternalAction<TTarget>(LTarget, LContainerInstance.Delete);
InternalFunction<TTarget>(LTarget, LContainerInstance.FirstOrDefaultInteger);
InternalFunction<TTarget>(LTarget, LContainerInstance.FirstOrDefault);
LContainerInterface := LContainerInstance;
// Workaround if you know the original class as per Scott van der Linden:
InternalAction<TTarget>(LTarget, (LContainerInterface as TContainer<TTarget>).Delete);
InternalFunction<TTarget>(LTarget, (LContainerInterface as TContainer<TTarget>).FirstOrDefaultInteger);
InternalFunction<TTarget>(LTarget, (LContainerInterface as TContainer<TTarget>).FirstOrDefault);
// Nicer workaround as per Stefan Glienke:
InternalAction<TTarget>(LTarget, procedure(const Value: TTarget) begin LContainerInterface.Delete(Value); end);
InternalFunction<TTarget>(LTarget, function(const Id: Integer): TTarget begin Result := LContainerInterface.FirstOrDefaultInteger(Id); end);
InternalFunction<TTarget>(LTarget, function(const Id: Integer): TTarget begin Result := LContainerInterface.FirstOrDefault(Id); end);
// Nicer workaround if you have many types like TTarget:
InternalAction<TTarget>(LTarget, Delete<TTarget>(LContainerInterface));
InternalFunction<TTarget>(LTarget, FirstOrDefaultInteger<TTarget>(LContainerInterface));
InternalFunction<TTarget>(LTarget, FirstOrDefault<TTarget>(LContainerInterface));
// Failure:
InternalAction<TTarget>(LTarget, LContainerInterface.Delete);
InternalFunction<TTarget>(LTarget, LContainerInterface.FirstOrDefaultInteger);
InternalFunction<TTarget>(LTarget, LContainerInterface.FirstOrDefault);
// Why these two errors on the above three lines?
//[dcc32 Error] UsageUnit.pas(146): E2035 Not enough actual parameters
//[dcc32 Error] UsageUnit.pas(147): E2035 Not enough actual parameters
//[dcc32 Error] UsageUnit.pas(148): E2250 There is no overloaded version of 'FirstOrDefault' that can be called with these arguments
end;
end. //////////////////////////////////////////////////////////////////////////////////////////////

using System;
namespace MethodReferencesSucceedOnInterfaces
{
// based on Delphi code in https://gist.github.com/jpluimers/6a3b9eadf96aa6db7958d11a7091b925
public class Target
{
}
public interface IContainer<T> where T : class
{
void Delete(T value);
T FirstOrDefault(int id);
T FirstOrDefault(string id);
T FirstOrDefaultInteger(int id);
T FirstOrDefaultString(string id);
}
public class Container<T> : IContainer<T> where T : class
{
public void Delete(T value)
{
}
public T FirstOrDefault(int id)
{
return null;
}
public T FirstOrDefault(string id)
{
return null;
}
public T FirstOrDefaultInteger(int id)
{
return null;
}
public T FirstOrDefaultString(string id)
{
return null;
}
}
public class Usage
{
private void InternalCallFunc<T>(T value, Func<int, T> function) where T : class
{
}
private void InternalCallAction<T>(T value, Action<T> action) where T : class
{
}
private void Run()
{
Target target = new Target();
Container<Target> containerInstance = new Container<Target>();
InternalCallAction<Target>(target, containerInstance.Delete);
InternalCallFunc<Target>(target, containerInstance.FirstOrDefaultInteger);
InternalCallFunc<Target>(target, containerInstance.FirstOrDefault);
IContainer<Target> containerInterface = containerInstance;
InternalCallAction<Target>(target, containerInterface.Delete);
InternalCallFunc<Target>(target, containerInterface.FirstOrDefaultInteger);
InternalCallFunc<Target>(target, containerInterface.FirstOrDefault);
}
}
}

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: