I see quite often in various C# code bases usages of the Equals method when comparing enums. This has quite a big performance impact, because this Equals comes from Enum.Equals who’s argument is of type ‘object’ and in order to call this method, the compiler needs to box the enum (value) int an Enum (object).

A look into the generated IL code shows the boxing of the enum value into a reference type, meaning an object is allocated which in this context is an expensive operation. Then the object.Equals method from the base class is called, which is a virtual call, also an expensive operation. (Not to mention that the GarbageCollector will have to collect the memory of the boxed object back.)

.method public hidebysig 
    instance bool ObjectEqualsMethod () cil managed 
{
    IL_0000: ldc.i4.2
    IL_0001: stloc.0
    IL_0002: ldloca.s 0
    IL_0004: ldc.i4.1
    IL_0005: box [System.Runtime]System.DateTimeKind
    IL_000a: constrained. [System.Runtime]System.DateTimeKind
    IL_0010: callvirt instance bool [System.Runtime]System.Object::Equals(object)
    IL_0015: ret
}

When using the == operator, no allocations or virtual calls are happening:

.method public hidebysig 
        instance bool EqualsOperator () cil managed 
{
    IL_0000: ldc.i4.0
    IL_0001: ret
} 

NOTE:
⚠️ When boxing a value, a small short-living object is allocated on the heap. Later, the GarbageCollector will have to collect this object.
When many such objects are allocated, and in between these allocations other longer-living objects are also allocated, when the GC will collect the memory occupied by these small objects, there will remain gaps between the longer living objects, leading to memory fragmentation.
At some point, the GC needs to do a defragmentation, that is, to move all long-living objects one after the other so that they occupy a continous chunk of memory. This is a very slow operation.

But how much of a performance issue is this?

Is this really a performance issue? Only benchmark.net can tell!

public class EnumEquality
{
    [Benchmark]
    public bool ObjectEqualsMethod() => DateTimeKind.Local.Equals(DateTimeKind.Utc);

    [Benchmark(Baseline = true)]
    public bool EqualsOperator() => DateTimeKind.Local == DateTimeKind.Utc;
}

The results are quite clear:

BenchmarkDotNet v0.13.8, Windows 11 (10.0.22621.2283/22H2/2022Update/SunValley2)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
  [Host]     : .NET Framework 4.8.1 (4.8.9181.0), X86 LegacyJIT
  DefaultJob : .NET Framework 4.8.1 (4.8.9181.0), X86 LegacyJIT


| Method             | Mean      | Error     | StdDev    | Median    | Ratio | RatioSD |
|------------------- |----------:|----------:|----------:|----------:|------:|--------:|
| ObjectEqualsMethod | 5.5424 ns | 0.0147 ns | 0.0123 ns | 5.5423 ns |     ? |       ? |
| EqualsOperator     | 0.0003 ns | 0.0009 ns | 0.0008 ns | 0.0000 ns |     ? |       ? |

This shows a more than 1000x performance impreove when using the == operator instead of the Equals method, although on less performant CPUs I’m getting only around 150x, but still, huge improvement especially when this occurs in many places or in inner critical loops.

Conclusion

For value types, always use == operator, unless it doesn’t give the desired results.

Avoid boxing whenever possible.