Enum HasFlag() Benchmark

Benchmark 1: Native Dotnet Method

In C# theres the possibility to just call Enum.HasFlag(enumMember). This method has been rewritten during the Release of dotnet 5. Thus the Benchmarks for net48 will suck really really bad. The reason behind this mess is due to the fact that it had to perform some checks and is not jit optimized

Benchmark 2: GenericContainsFlag

Flagged enums are actually kinda simple. They’re only binary values. Flag A = 0x0001 Flag B = 0x0010 -> Flag A AND Flag B = 0x0011

So its sufficient to perform a logical OR operation:

  0x0011
& 0x0100
= 0x0000 // Does not contain this EnumMember

  0x0011
& 0x0010
= 0x0010 // Contains this EnumMember

This generic approach has to deal with the same problem as the native implementation. FlagChecks can only work on enum classes which contan the [Flags] attribute.

I’ll go for a cast here and I expect this to peform even worse than dotnets native method

    public static bool ContainsFlagGeneric<T>(this T @enum,
                                       T flagToTest)
        where T: Enum
        => (Convert.ToInt32(@enum) & Convert.ToInt32(flagToTest)) != 0;

Benchmark 3: ExplicitContainsFlag

When the EnumType is known ( and thus its ensured that it contains the attribute ) I can directly perform the logical AND . While this implementation will outperform both other Methods by large using net48 it might perform worse on more recent targetFrameworks.

    public static bool ContainsFlag(this TestEnum testEnum,
                                    TestEnum flag)
        => (testEnum & flag) != 0;

Results

Well everything as expected

MethodJobRuntimeTestEnumMeanErrorStdDevMedianGen0Allocated
DotnetsHasFlag.NET 6.0.NET 6.0X10.0024 ns0.0041 ns0.0039 ns0.0000 ns--
ContainsFlagGeneric.NET 6.0.NET 6.0X130.0068 ns0.1473 ns0.1306 ns30.0199 ns0.007696 B
ContainsFlagExplicit.NET 6.0.NET 6.0X10.0077 ns0.0045 ns0.0042 ns0.0064 ns--
DotnetsHasFlag.NET 8.0.NET 8.0X10.0003 ns0.0005 ns0.0005 ns0.0000 ns--
ContainsFlagGeneric.NET 8.0.NET 8.0X118.3309 ns0.0862 ns0.0720 ns18.3499 ns0.007696 B
ContainsFlagExplicit.NET 8.0.NET 8.0X10.0002 ns0.0004 ns0.0003 ns0.0000 ns--
DotnetsHasFlag.NET Framework 4.8.NET Framework 4.8X19.5784 ns0.0195 ns0.0173 ns9.5734 ns0.007648 B
ContainsFlagGeneric.NET Framework 4.8.NET Framework 4.8X148.1679 ns0.1372 ns0.1216 ns48.1179 ns0.015396 B
ContainsFlagExplicit.NET Framework 4.8.NET Framework 4.8X10.0102 ns0.0056 ns0.0053 ns0.0111 ns--
DotnetsHasFlag.NET 6.0.NET 6.0X1, X2, X40.0080 ns0.0052 ns0.0049 ns0.0087 ns--
ContainsFlagGeneric.NET 6.0.NET 6.0X1, X2, X429.6568 ns0.0660 ns0.0617 ns29.6794 ns0.007696 B
ContainsFlagExplicit.NET 6.0.NET 6.0X1, X2, X40.0006 ns0.0024 ns0.0022 ns0.0000 ns--
DotnetsHasFlag.NET 8.0.NET 8.0X1, X2, X40.0011 ns0.0014 ns0.0013 ns0.0005 ns--
ContainsFlagGeneric.NET 8.0.NET 8.0X1, X2, X420.8817 ns0.0855 ns0.0714 ns20.8671 ns0.007696 B
ContainsFlagExplicit.NET 8.0.NET 8.0X1, X2, X40.0003 ns0.0006 ns0.0006 ns0.0000 ns--
DotnetsHasFlag.NET Framework 4.8.NET Framework 4.8X1, X2, X410.0027 ns0.0244 ns0.0216 ns10.0089 ns0.007648 B
ContainsFlagGeneric.NET Framework 4.8.NET Framework 4.8X1, X2, X448.1968 ns0.1167 ns0.1035 ns48.1573 ns0.015396 B
ContainsFlagExplicit.NET Framework 4.8.NET Framework 4.8X1, X2, X40.0076 ns0.0029 ns0.0027 ns0.0076 ns--
DotnetsHasFlag.NET 6.0.NET 6.0X1, X(…) X512 [47]0.0144 ns0.0063 ns0.0056 ns0.0145 ns--
ContainsFlagGeneric.NET 6.0.NET 6.0X1, X(…) X512 [47]29.5644 ns0.0550 ns0.0488 ns29.5493 ns0.007696 B
ContainsFlagExplicit.NET 6.0.NET 6.0X1, X(…) X512 [47]0.0012 ns0.0021 ns0.0020 ns0.0000 ns--
DotnetsHasFlag.NET 8.0.NET 8.0X1, X(…) X512 [47]0.0000 ns0.0001 ns0.0001 ns0.0000 ns--
ContainsFlagGeneric.NET 8.0.NET 8.0X1, X(…) X512 [47]20.8049 ns0.0664 ns0.0588 ns20.8043 ns0.007696 B
ContainsFlagExplicit.NET 8.0.NET 8.0X1, X(…) X512 [47]0.0006 ns0.0010 ns0.0008 ns0.0000 ns--
DotnetsHasFlag.NET Framework 4.8.NET Framework 4.8X1, X(…) X512 [47]9.9909 ns0.0198 ns0.0165 ns9.9934 ns0.007648 B
ContainsFlagGeneric.NET Framework 4.8.NET Framework 4.8X1, X(…) X512 [47]48.0942 ns0.0702 ns0.0622 ns48.0951 ns0.015396 B
ContainsFlagExplicit.NET Framework 4.8.NET Framework 4.8X1, X(…) X512 [47]0.0060 ns0.0041 ns0.0038 ns0.0067 ns--
DotnetsHasFlag.NET 6.0.NET 6.0X1, X(…)X1024 [54]0.0051 ns0.0049 ns0.0046 ns0.0045 ns--
ContainsFlagGeneric.NET 6.0.NET 6.0X1, X(…)X1024 [54]27.9598 ns0.0830 ns0.0777 ns27.9430 ns0.007696 B
ContainsFlagExplicit.NET 6.0.NET 6.0X1, X(…)X1024 [54]0.0034 ns0.0047 ns0.0044 ns0.0011 ns--
DotnetsHasFlag.NET 8.0.NET 8.0X1, X(…)X1024 [54]0.0000 ns0.0001 ns0.0001 ns0.0000 ns--
ContainsFlagGeneric.NET 8.0.NET 8.0X1, X(…)X1024 [54]20.5399 ns0.1075 ns0.1006 ns20.5378 ns0.007696 B
ContainsFlagExplicit.NET 8.0.NET 8.0X1, X(…)X1024 [54]0.0004 ns0.0009 ns0.0008 ns0.0000 ns--
DotnetsHasFlag.NET Framework 4.8.NET Framework 4.8X1, X(…)X1024 [54]9.9675 ns0.0257 ns0.0228 ns9.9658 ns0.007648 B
ContainsFlagGeneric.NET Framework 4.8.NET Framework 4.8X1, X(…)X1024 [54]47.9990 ns0.1296 ns0.1149 ns47.9698 ns0.015396 B
ContainsFlagExplicit.NET Framework 4.8.NET Framework 4.8X1, X(…)X1024 [54]0.0055 ns0.0037 ns0.0034 ns0.0054 ns--

Testcode

Full SourceCode
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
 
namespace Benchmarkz;
 
[SimpleJob(RuntimeMoniker.Net48)]
[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.Net80)]
[MemoryDiagnoser(displayGenColumns: true)]
public class EnumFlags
{
[Params(0x0001,
        0x0007,
        0x03FF,
        0x07FF)]
public TestEnum TestEnum;
 
    [Benchmark]
    public void DotnetsHasFlag() => _ = TestEnum.HasFlag(TestEnum.X4);
    
    [Benchmark]
    public void ContainsFlagGeneric() => _ = TestEnum.ContainsFlagGeneric(TestEnum.X4);
    
    [Benchmark]
    public void ContainsFlagExplicit() => _ = TestEnum.ContainsFlag(TestEnum.X4);
 
}
 
public static class EnumExtensions
{
    public static bool ContainsFlagGeneric<T>(this T @enum,
                                              T flagToTest) where T: Enum
        => (Convert.ToInt32(@enum) & Convert.ToInt32(flagToTest)) != 0;
 
    public static bool ContainsFlag(this TestEnum testEnum,
                                    TestEnum flag) 
        => (testEnum & flag) != 0;
}
 
 
[Flags]
public enum TestEnum
{
X0 = 0,
X1 = 1,
X2 = 1 << 1,
X4 = 1 << 2,
X8 = 1 << 3,
X16 = 1 << 4,
X32 = 1 << 5,
X64 = 1 << 6,
X128 = 1 << 7,
X265 = 1 << 8,
X512 = 1 << 9,
X1024 = 1 << 10,
}