Search code examples
c#extension-methods

Is it possible for an extension method to be inlined in C#?


I have a simple, fairly common extension method for string.IsNullOrEmpty:

using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;

public class Program
{
    public static void Main()
    {
        var str = "string!";
        
        Console.WriteLine(string.IsNullOrEmpty(str));
        
        Console.WriteLine(str.IsNullOrEmpty());
    }
}

public static class StringExtensions
{
    public static bool IsNullOrEmpty([NotNullWhen(false)] this string value)
    {
        return string.IsNullOrEmpty(value);
    }
}

According to ReSharper, the IL in Release config (Any CPU) for these methods is:

main:
  .method public hidebysig static void
    Main() cil managed
  {
    .entrypoint
    .maxstack 8

    // [8 9 - 8 29]
    IL_0000: ldstr        "string!"

    // [10 9 - 10 54]
    IL_0005: dup
    IL_0006: call         bool [System.Runtime]System.String::IsNullOrEmpty(string)
    IL_000b: call         void [System.Console]System.Console::WriteLine(bool)

    // [12 9 - 12 48]
    IL_0010: call         bool StringExtensions::IsNullOrEmpty(string)
    IL_0015: call         void [System.Console]System.Console::WriteLine(bool)

    // [13 5 - 13 6]
    IL_001a: ret

  } // end of method Program::Main

IsNullOrEmpty:
  .method public hidebysig static bool
    IsNullOrEmpty(
      string 'value'
    ) cil managed
  {
    .custom instance void System.Runtime.CompilerServices.NullableContextAttribute::.ctor(unsigned int8)
      = (01 00 02 00 00 ) // .....
      // unsigned int8(2) // 0x02
    .custom instance void [System.Runtime]System.Runtime.CompilerServices.ExtensionAttribute::.ctor()
      = (01 00 00 00 )
    .param [1]
      .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.NotNullWhenAttribute::.ctor(bool)
        = (01 00 00 00 00 ) // .....
        // bool(false)
    .maxstack 8

    // [20 9 - 20 44]
    IL_0000: ldarg.0      // 'value'
    IL_0001: call         bool [System.Runtime]System.String::IsNullOrEmpty(string)
    IL_0006: ret

  } // end of method StringExtensions::IsNullOrEmpty

Adding [MethodImpl(MethodImplOptions.AggressiveInlining)] or any other seemingly appropriate MethodImplOptions has no effect. Removing [NotNullWhen(false)]has no effect.

This method is simple enough that I'd have assumed the compiler would just replace the call on bool StringExtensions::IsNullOrEmpty(string) with the bool [System.Runtime]System.String::IsNullOrEmpty(string).

Is there nothing to be gained by replacing it? Sure, it's just adding a before the call to string.IsNullOrEmpty, but it's a relatively trivial substitution, right?


Solution

  • Extension methods are just "syntactic sugar".

    From the jitters point of view there is no difference between an extension method call and any other method call. But the inlining will be done by the jitter, so you need to inspect the actual assembly code from the jitter, not the IL code produced by the compiler.

    The rules for what may be inlined or not may also vary between jitter versions, so you may need to inspect the output for each jitter version you are interested in.