Inlining methods in C#

Inlining is a simple process of, essentially replacing a method call with the contents of the call, for example in C++ we might have something like

class Math {
public:
    inline int square(int x);
};

inline int Math::square(int x) {
    return x * x;
}

Math m;
int y = m.square(5);

where the square method might be compiled down to

int y = 5 * 5;

This post isn’t about C++ inlining but there are similarities with C# in that a method may be implicitly inline, i.e. no need for the inline keyword and where we might request a method to be inlined – the compiler in this case may or may not fulfil the request.

In C# the same implicit inlining can be seen with code such as

public static int Square(int x) => x * x;

and again we can request or hint to the JIT to inline – this is the key thing to take away from this post, we can request inlining but essentially the compiler will make the decision on whether it should inline our code.

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Square(int x) => x * x;

What cannot be inlined?

  • Methods which are too large, i.e. a method body which exceeds JITA internal size threshold.
  • Virtual/interfaces calls cannot be inlined unless the JIT can de-virtualize them.
  • Generic methods with unresolved types.
  • try/catch/finally blocks are no eligible for inlining
  • async/iterator methods i.e. async/await and yield return both compile to a state machine which cannot be inlined.
  • Methods with lock keyword, this essentially compiles to Monitor.Enter/Monitor.Exit with try/finally and hence cannot be inlined.
  • Methods with stackalloc cannot be inlined.
  • Methods with unsafe and some other pointer operations cannot be inlined.
  • Might seem obvious but marking your methods as MethodImplOptions.NoInlining will not allow this method to be inlined.
  • P/Invoke and extern methods.
  • Inlining may be disabled for debug builds, and other compiler optimizations