Home > front end >  Why is my Extension Method overload not preferred?
Why is my Extension Method overload not preferred?

Time:01-19

I made a generic overload for Enum.HasFlag that prevents boxing:

    public static unsafe bool HasFlag<T>(this T enumVal, T flag) where T : unmanaged, Enum {
        return sizeof(T) switch {
            1 => (*(byte*)&enumVal & *(byte*)&flag) == *(byte*)&flag,
            2 => (*(ushort*)&enumVal & *(ushort*)&flag) == *(ushort*)&flag,
            4 => (*(uint*)&enumVal & *(uint*)&flag) == *(uint*)&flag,
            8 => (*(ulong*)&enumVal & *(ulong*)&flag) == *(ulong*)&flag,
            _ => throw new ArgumentException("Unsupported base enum Type")
        };
    }

Yet the compiler still wants to use the default Enum.HasFlag instead and I have to explicitly define the generic type to force it to use the extension.

The extension method should take priority here as the parameters have the correct type and do not need implicit type casting compared to the original one so why is the compiler still using the wrong one?

CodePudding user response:

For details on the overload resolution process, see §12.6.4 of the specification. Right at the bottom of the general description of the process in §12.7.6.1, you can see:

Otherwise, an attempt is made to process E.I as an extension method invocation (§12.7.8.3). If this fails, E.I is an invalid member reference, and a binding-time error occurs.

If we take a look at §12.7.8.3:

if the normal processing of the invocation finds no applicable methods, an attempt is made to process the construct as an extension method invocation

This is pretty clear that an attempt is made to bind an extension method only if the overload resolution process fails to find an applicable instance method.

This is a deliberate decision. If this were not the case, adding a single using statement to the top of a file could change how methods are bound further down in the file -- spooky action at a distance, which the spec generally tries to avoid.


However, since .NET Core 2.1, Enum.HasFlag has been a JIT intrinsic (it was the poster-child for which the JIT intrinsics mechanism was introduced). This means that although the IL may say to box and call the Enum.HasFlag method, in reality the JIT knows that it can replace this with a single bitwise test.

For example, the code:

public void Native(StringSplitOptions o) {
    if (o.HasFlag(StringSplitOptions.RemoveEmptyEntries))
    {
        Console.WriteLine("Noo");   
    }
}

Is jitted to this assembly in Release:

C.Native(System.StringSplitOptions)
    L0000: test dl, 1
    L0003: je short L0017
    L0005: mov rcx, 0x1ac4adebda0
    L000f: mov rcx, [rcx]
    L0012: jmp 0x00007ffb2f6ff7f8
    L0017: ret

No sign of any method calls there (apart from the final Console.WriteLine at the end)!

The same code using your extension method is significantly worse:

public void Worse(StringSplitOptions o) {
    if (o.HasFlag<StringSplitOptions>(StringSplitOptions.RemoveEmptyEntries))
    {
        Console.WriteLine("Noo");   
    }
}

Gives:

C.Worse(System.StringSplitOptions)
    L0000: sub rsp, 0x28
    L0004: mov [rsp 0x24], edx
    L0008: mov dword ptr [rsp 0x20], 1
    L0010: mov ecx, [rsp 0x24]
    L0014: and ecx, [rsp 0x20]
    L0018: cmp ecx, [rsp 0x20]
    L001c: sete cl
    L001f: movzx ecx, cl
    L0022: test ecx, ecx
    L0024: je short L0038
    L0026: mov rcx, 0x1ac4adebda0
    L0030: mov rcx, [rcx]
    L0033: call 0x00007ffb2f6ff7f8
    L0038: nop
    L0039: add rsp, 0x28
    L003d: ret

With the additional cost of your extension method itself:

Extensions.HasFlag[[System.StringSplitOptions, System.Private.CoreLib]](System.StringSplitOptions, System.StringSplitOptions)
    L0000: mov [rsp 8], ecx
    L0004: mov [rsp 0x10], edx
    L0008: mov eax, [rsp 8]
    L000c: and eax, [rsp 0x10]
    L0010: cmp eax, [rsp 0x10]
    L0014: sete al
    L0017: movzx eax, al
    L001a: ret

Since your question is tagged [.net-6.0], the best advice is to throw away your extension method and use the built-in Enum.HasFlag, as it's significantly faster than what you've written.

See this all on SharpLab.

  •  Tags:  
  • Related