Why does the is-operator cause unnecessary boxing?

Viewed 550

The documentation of constant pattern matching with the is-operator (expr is constant) states:

The constant expression is evaluated as follows:

  1. If expr and constant are integral types, the C# equality operator determines whether the expression returns true (that is, whether expr == constant).

  2. Otherwise, the value of the expression is determined by a call to the static Object.Equals(expr, constant) method.


Therefore, when using this code

public bool IsZero(int value)
{
    return value is 0;
}

I expect it to use the == operator (case 1) and generate this code:

.method public hidebysig instance bool
    IsZero(
       int32 'value'
    ) cil managed
{
    .maxstack 8

    ldarg.1
    ldc.i4.0
    ceq
    ret
}

However, in reality, the integer parameter and the constant (literal) are boxed in order to be passed to the static Object.Equals method (case 2):

.method public hidebysig instance bool
    IsZero(
       int32 'value'
    ) cil managed
{
    .maxstack 8

    ldc.i4.0
    box          [mscorlib]System.Int32
    ldarg.1
    box          [mscorlib]System.Int32
    call         bool [mscorlib]System.Object::Equals(object, object)
    ret
}

Why is that the case?

2 Answers

The compiler is the same in all cases - Roslyn. Different versions produce different IL though. The C# 8 versions don't box, while older ones do.

For example, with 2.9.0 the IL for this snippet :

using System;
public class C {

    public bool IsZero(int value)
    {
        return value is 0;
    }
}

is

    IL_0000: nop
    IL_0001: ldc.i4.0
    IL_0002: box [mscorlib]System.Int32
    IL_0007: ldarg.1
    IL_0008: box [mscorlib]System.Int32
    IL_000d: call bool [mscorlib]System.Object::Equals(object, object)
    IL_0012: stloc.0
    IL_0013: br.s IL_0015

    IL_0015: ldloc.0
    IL_0016: ret

Using any of the C# 8 versions though produces this in debug mode :

    IL_0000: nop
    IL_0001: ldarg.1
    IL_0002: ldc.i4.0
    IL_0003: ceq
    IL_0005: stloc.0
    IL_0006: br.s IL_0008

    IL_0008: ldloc.0
    IL_0009: ret

and this in Release.

    IL_0000: ldarg.1
    IL_0001: ldc.i4.0
    IL_0002: ceq
    IL_0004: ret

That's the same as the expected code in the question

is operator Documentation states:

When performing pattern matching with the constant pattern, is tests whether an expression equals a specified constant. In C# 6 and earlier versions, the constant pattern is supported by the switch statement. Starting with C# 7.0, it's supported by the is statement as well.

By default VS2017 using older version C# compiler. You can enable C# 7.0 features by installing Microsoft.Net.Compilers from NuGet which can be used to compile the code with the latest version of the compiler.

Related