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!
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
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:
Compile it with:
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);
}
}
```