Description
Currently, the requirements for delegates are (from ECMA-335 §II.14.6 Delegates):
Delegates shall be declared sealed, and the only members a delegate shall have are [the] methods as specified here.
What if this requirement was weakened, so that delegates were allowed to be unsealed and also allowed to contain other members, especially fields? I think it would allow the C# compiler (and other compilers) to reduce the number of allocations required for lambdas that close over local variables, by fusing the delegate and the closure into a single object. (It also might allow F# to start using delegate types instead of FSharpFunc
, but I'm not sure about that.)
For example, consider the following C# code:
delegate int FuncInt();
void M()
{
int i = 42;
FuncInt f = () => i;
f();
}
Currently, the generated code looks roughly like this:
sealed class FuncInt : MulticastDelegate
{
public extern FuncInt(object obj, IntPtr method);
public virtual extern int Invoke();
}
sealed class Closure
{
public int i;
internal int Main_f() => this.i;
}
void Main()
{
Closure closure = new Closure();
closure.i = 42;
FuncInt f = new FuncInt(closure, __methodptr(Closure.Main_f));
f.Invoke();
}
Notice that there are two allocations for a single lambda: the closure and the delegate.
If the requirements for delegates were weakened, the generated code could instead be:
class FuncInt : MulticastDelegate // no longer sealed
{
public extern FuncInt(object obj, IntPtr method);
public virtual extern int Invoke();
}
sealed class Closure : FuncInt // delegate class containing fields and custom methods
{
public Closure() : base(this, __methodptr(Main_f)) {}
public int i;
private int Main_f() => this.i;
}
void Main()
{
Closure closure = new Closure();
closure.i = 42;
FuncInt f = closure;
f.Invoke();
}
This way, the two allocations are fused, which should make the code more efficient.
Some other notes:
- In cases when a single closure is used for multiple lambdas, this would still avoid one delegate allocation (but not the others).
- I think this also shouldn't interfere with the multicasting nature of delegates, since the runtime-provided fields would still be there.
- Since I believe that unsealing a class is not a breaking change, unsealing framework-provided delegate types (like the
Func
andAction
types) in some future version of the framework should be fine. Existing code would then become more efficient just by using a newer compiler and targeting a newer framework.
What do you think?