Runtime: Proposal: Allow delegates to implement single-interface methods.

Created on 26 Aug 2016  路  4Comments  路  Source: dotnet/runtime

Issue https://github.com/dotnet/corefx/issues/11173 and a discussion with @stephentoub prompted me to file this issue to get some knowledgeable eyes to look at an idea I had.

Specifically, in the course of that bug being fixed I was made aware that the CoreFx implements Array.Sort(Comparison<T>) by wrapping the Comparison in an IComparer<T> and forwarding along to another Sort method. This unfortunately means an allocation, and i'm all for eliminating allocations when not necessary.

The idea i had was as follows: Allow delegates to implement interfaces with single methods on them. i.e. I should be able to say:

c# public delegate int Comparison<T>(T t1, T t2) : IComparer<T>

I have an idea about how this would work, but i wanted to run it by people here to see if there would be any roadblocks that would get in the way. It's my understanding (and please correct me if I'm wrong) that a delegate is mostly just a class that inherits from System.Delegate or System.MultiCastDelegate. It has a compiler provided Invoke method with the signature specified. It seems to me that this class could just implement the specified interface, and that the .Invoke method would fill in the slot for the interface table in that class.

My hope would be that this would then 'just work'. The Comparison<T> instance could then be passed freely to methods that take IComparer<T>s. I'd like to play around in IL later to see if that's the case. However, I wanted to run it by the people here to see if there's any concern at the CLR level about if this is possible or not. Are there special shenanigans (or other delegate specializations) going on that would make this approach not work? Or does this seem like an easy thing to do that would provide for a small alloc win. In the case i ran into, this was actually a hot path. The delegate was cached, but it never occurred to me that the runtime would then be adding an Alloc each time i called .Sort.

Thanks for your help!

area-TypeSystem-coreclr enhancement

Most helpful comment

At least with a fairly simple test, it looks like there's little preventing this from being done purely in the language compiler.

Here's a simple little app:

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}
.assembly test
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module test.exe
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001

// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi sealed MyDelegate extends [mscorlib]System.MulticastDelegate implements IMyInterface
{  
  .method public hidebysig specialname rtspecialname instance void  .ctor(object 'object', native int 'method') runtime managed
  {
  } // end of method MyDelegate::.ctor

  .method public hidebysig newslot virtual instance int32  Invoke(int32 a, int32 b) runtime managed
  {
      .override [test]IMyInterface::DoWork
  } // end of method MyDelegate::Invoke

  .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(int32 a, int32 b, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
  {
  } // end of method MyDelegate::BeginInvoke

  .method public hidebysig newslot virtual instance int32  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
  {
  } // end of method MyDelegate::EndInvoke

} // end of class MyDelegate

.class interface public abstract auto ansi IMyInterface
{
  .method public hidebysig newslot abstract virtual instance int32  DoWork(int32 a, int32 b) cil managed
  {
  } // end of method IMyInterface::DoWork

} // end of class IMyInterface

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main() cil managed
  {
    .entrypoint
    // Code size       26 (0x1a)
    .maxstack  2
    .locals init (class MyDelegate V_0)
    IL_0000:  nop
    IL_0001:  ldnull
    IL_0002:  ldftn      int32 Test::Add(int32, int32)
    IL_0008:  newobj     instance void MyDelegate::.ctor(object, native int)
    IL_000d:  call       void Test::CallMethod(class IMyInterface)
    IL_0012:  nop
    IL_0013:  ret
  } // end of method Test::Main

  .method public hidebysig static void  CallMethod(class IMyInterface m) cil managed
  {
    // Code size       16 (0x10)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldc.i4.1
    IL_0003:  ldc.i4.2
    IL_0004:  callvirt   instance int32 IMyInterface::DoWork(int32, int32)
    IL_0009:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_000e:  nop
    IL_000f:  ret
  } // end of method Test::CallMethod

  .method public hidebysig static int32  Add(int32 a, int32 b) cil managed
  {
    // Code size       4 (0x4)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldarg.1
    IL_0002:  add
    IL_0003:  ret
  } // end of method Test::Add

  .method public hidebysig specialname rtspecialname  instance void  .ctor() cil managed
  {
    // Code size       8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Test::.ctor

} // end of class Test

Compile it with:

ilasm /out=test.exe test.il

and it runs fine. The above is basically this:

``` C#
using System;

public delegate int MyDelegate(int a, int b);

public interface IMyInterface
{
int DoWork(int a, int b);
}

public class Test
{
public static void Main()
{
MyDelegate d = Add;
CallMethod(d);
}

public static void CallMethod(IMyInterface m)
{
    Console.WriteLine(m.DoWork(1, 2));
}

public static int Add(int a, int b) => a + b;

}
```

All 4 comments

At least with a fairly simple test, it looks like there's little preventing this from being done purely in the language compiler.

Here's a simple little app:

.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
  .ver 4:0:0:0
}
.assembly test
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) 
  .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 )
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module test.exe
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003
.corflags 0x00000001

// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi sealed MyDelegate extends [mscorlib]System.MulticastDelegate implements IMyInterface
{  
  .method public hidebysig specialname rtspecialname instance void  .ctor(object 'object', native int 'method') runtime managed
  {
  } // end of method MyDelegate::.ctor

  .method public hidebysig newslot virtual instance int32  Invoke(int32 a, int32 b) runtime managed
  {
      .override [test]IMyInterface::DoWork
  } // end of method MyDelegate::Invoke

  .method public hidebysig newslot virtual instance class [mscorlib]System.IAsyncResult BeginInvoke(int32 a, int32 b, class [mscorlib]System.AsyncCallback callback, object 'object') runtime managed
  {
  } // end of method MyDelegate::BeginInvoke

  .method public hidebysig newslot virtual instance int32  EndInvoke(class [mscorlib]System.IAsyncResult result) runtime managed
  {
  } // end of method MyDelegate::EndInvoke

} // end of class MyDelegate

.class interface public abstract auto ansi IMyInterface
{
  .method public hidebysig newslot abstract virtual instance int32  DoWork(int32 a, int32 b) cil managed
  {
  } // end of method IMyInterface::DoWork

} // end of class IMyInterface

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main() cil managed
  {
    .entrypoint
    // Code size       26 (0x1a)
    .maxstack  2
    .locals init (class MyDelegate V_0)
    IL_0000:  nop
    IL_0001:  ldnull
    IL_0002:  ldftn      int32 Test::Add(int32, int32)
    IL_0008:  newobj     instance void MyDelegate::.ctor(object, native int)
    IL_000d:  call       void Test::CallMethod(class IMyInterface)
    IL_0012:  nop
    IL_0013:  ret
  } // end of method Test::Main

  .method public hidebysig static void  CallMethod(class IMyInterface m) cil managed
  {
    // Code size       16 (0x10)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldarg.0
    IL_0002:  ldc.i4.1
    IL_0003:  ldc.i4.2
    IL_0004:  callvirt   instance int32 IMyInterface::DoWork(int32, int32)
    IL_0009:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_000e:  nop
    IL_000f:  ret
  } // end of method Test::CallMethod

  .method public hidebysig static int32  Add(int32 a, int32 b) cil managed
  {
    // Code size       4 (0x4)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  ldarg.1
    IL_0002:  add
    IL_0003:  ret
  } // end of method Test::Add

  .method public hidebysig specialname rtspecialname  instance void  .ctor() cil managed
  {
    // Code size       8 (0x8)
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Test::.ctor

} // end of class Test

Compile it with:

ilasm /out=test.exe test.il

and it runs fine. The above is basically this:

``` C#
using System;

public delegate int MyDelegate(int a, int b);

public interface IMyInterface
{
int DoWork(int a, int b);
}

public class Test
{
public static void Main()
{
MyDelegate d = Add;
CallMethod(d);
}

public static void CallMethod(IMyInterface m)
{
    Console.WriteLine(m.DoWork(1, 2));
}

public static int Add(int a, int b) => a + b;

}
```

Is this heading to anonymous classes?

CC @jkotas @kouvel

Closing as this doesnt appear to require coreclr support

Was this page helpful?
0 / 5 - 0 ratings

Related issues

bencz picture bencz  路  3Comments

Timovzl picture Timovzl  路  3Comments

btecu picture btecu  路  3Comments

sahithreddyk picture sahithreddyk  路  3Comments

v0l picture v0l  路  3Comments