Почему оператор is вызывает ненужный бокс?

Документация сопоставления константных шаблонов с состояниями is -operator (expr is constant):

Постоянное выражение оценивается следующим образом:

  1. Если expr и constant являются целочисленными типами, оператор равенства С# определяет, возвращает ли выражение значение true (то есть является ли expr == constant).

  2. В противном случае значение выражения определяется путем вызова статического метода Object.Equals(expr, constant).


Поэтому при использовании этого кода

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

Я ожидаю, что он будет использовать оператор == (случай 1) и сгенерировать этот код:

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

    ldarg.1
    ldc.i4.0
    ceq
    ret
}

Однако в действительности целочисленный параметр и константа (литерал) заключены в Object.Equals для передачи в статический метод Object.Equals (случай 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
}

Почему это так?

Ответ 1

Компилятор одинаков во всех случаях - Roslyn. Разные версии производят разные IL, хотя. Версии С# 8 не коробятся, в то время как старые делают.

Например, с 2.9.0 IL для этого фрагмента:

using System;
public class C {

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

является

    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

Использование любой из версий С# 8, хотя производит это в режиме отладки:

    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

и это в выпуске.

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

Это так же, как ожидаемый код в вопросе

Ответ 2

Документация оператора гласит:

При выполнении по шаблону с постоянным рисунком, is тесты, равняется ли выражение с заданным постоянным. В C# 6 и более ранних версиях шаблон констант поддерживается оператором switch. Начиная с C# 7.0, он также поддерживается оператором is.

По умолчанию VS2017 использует более старую версию C# компилятора. Вы можете включить функции C# 7.0, установив Microsoft.Net.Compilers from NuGet, который можно использовать для компиляции кода с последней версией компилятора.