This project is read-only.
1

Resolved

C# emit: suboptimal representation of static closures

description

With current 'master' build, code like this:
class C {
    event System.Action E = delegate { };
}
Compiles down to (with optimizations enabled):
class C {
    [CompilerGenerated]
    private sealed class <>c__DisplayClass0 {
        public static readonly C.<>c__DisplayClass0 CS$<>9__inst;
        public static Action CS$<>9__CachedAnonymousMethodDelegate2;

        static <>c__DisplayClass0() {
            C.<>c__DisplayClass0.CS$<>9__inst = new C.<>c__DisplayClass0();
        }

        internal void <_ctor>b__1() { }
    }

    [CompilerGenerated]
    private event Action E;

    public C() {
        Action arg_20_1;
        if (arg_20_1 = C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 == null) {
            arg_20_1 = C.<>c__DisplayClass0.CS$<>9__CachedAnonymousMethodDelegate2 = new Action(C.<>c__DisplayClass0.CS$<>9__inst.<_ctor>b__1);
        }

        this.E = arg_20_1;
        base();
    }
}
We can see display class (with singleton instance) produced from anonymous method expression without closure.

Expected result is delegate { } compiled into simple static method, without blowing up metadata with useless display classes and their static instances...

comments

ControlFlow wrote Nov 3, 2014 at 12:20 AM

Ok, found explanation here: https://roslyn.codeplex.com/SourceControl/changeset/3de67f36894b8c93431619c256d77cc9cd2e291a

Still, emitting 1 class + 2 fields + 2 methods per each simplest static closure feels like overkill.
Do performance benefit of invocation really worth so many metadata to emit?

The only good thing is delegate caching in generic methods now works...

VSadov wrote Nov 5, 2014 at 10:29 PM

The change is indeed intentional and the perf improvements are fairly measurable. Even in some nongeneric cases. There are more explanations in https://roslyn.codeplex.com/workitem/246

Note that the per-lambda caching field is not new - compiler always tried to cache static lambdas. The implementing method is also required one way or another.
The new metadata additions are 1 class, 1 field and 1 method. The good part is that these additions are per-type (unless container is a generic method). So in general, one would need to have a lot of types containing just one static lambda with a very simple method body to notice metadata increase from this change.

So far it seems that per-invocation improvements in performance justify per-type expense in metadata

.