Click here to Skip to main content
Click here to Skip to main content

.NET's Enum.HasFlag and performance costs

, 19 Nov 2012
Rate this:
Please Sign up or sign in to vote.
Discusses Enum.HasFlag() and its performance cost as well as alternatives

Introduction

In .NET 4.0, Microsoft introduced the Enum.HasFlag() method to the .NET libraries. While this function is extremely useful, it has some costs anyone invoking it should be aware of as it does not match up to its standard bit-check equivalent.

Background

If you're familiar with bit flags, then you likely know the typical way of checking if a bit flag exists in a given value is usually done like so:

if ((value & flag) == flag) 

Let's take a look at the disassembly of Enum.HasFlag()

public bool HasFlag(Enum flag)
{
    if (!base.GetType().IsEquivalentTo(flag.GetType()))
    {
        throw new ArgumentException(Environment.GetResourceString(
                      "Argument_EnumTypeDoesNotMatch", new object[]
        {
            flag.GetType(),
            base.GetType()
        }));
    }

    ulong num = Enum.ToUInt64(flag.GetValue());
    ulong num2 = Enum.ToUInt64(this.GetValue());
    return (num2 & num) == num;

}

The first thing to note is the type-safety check. HasFlag() ensures that both your flag, this, and flag are of the same enumeration type. This is likely the most costly factor in regards to HasFlag().

The second thing to note is that when you pass a value to HasFlag() it is boxed to an Enum type. If you are not familiar with what boxing is, I suggest reading the following. This is another of the few things that affect the performance of HasFlag().

The third thing to look at is the two calls to Enum.ToUInt64(). GetValue() returns an object, so the value of your enumeration must first be boxed to an object and then unboxed by Enum.ToUInt64(). If you're wondering why they use UInt64, it's because it's the largest primitive data-type an enumeration can be. This is to ensure HasFlag() works for all enumeration types.

Lastly, we see the method ends with what we expected, the standard bit check. To sum up what we said above into a few short points about why HasFlag() has performance costs, it's due to:

  • Type verification/check
  • Lots of boxing/unboxing

Alternatives

The first alternative (and the most obvious) would be to use the standard check. This method isn't as nice looking as HasFlag() but it definitely is the most efficient.

[Flags]
public enum ExampleEnum
{
    A = 1 << 0,
    B = 1 << 1,
    C = 1 << 2,
    D = 1 << 3,
    E = 1 << 4,
}

public class Program
{
    static void Main(string[] args)
    {
        var foo = ExampleEnum.A | ExampleEnum.B | ExampleEnum.C;
        if ((foo & ExampleEnum.B) == ExampleEnum.B) // contains flag B!
        {

        }
    }
}

The second alternative would be to create an extension method for your enumeration. However, this can quickly get tedious if you have a lot of enumerations that you wish to check. This method is still more efficient than using Enum.HasFlag() as it does not involve any boxing and it does not require any type verification.

public static bool HasBitFlag(this ExampleEnum e, ExampleEnum other)
{
     return ((e & other) == other);
}

A third more generic (no pun intended, I swear!) solution would be to create a method such as the following:

public static bool HasBitFlag<T>(T e, T other)
     where T : IConvertible
{
     var eFlag = Convert.ToUInt64(e);
     var otherFlag = Convert.ToUInt64(other);
     return ((eFlag & otherFlag) == otherFlag);
}

This is a bit better than Enum.HasFlag() because it does not require type-safety verification. However, it should be noted both e and other are boxed to objects when invoking Convert.ToUInt64().

The fourth and final alternative requires inline IL. This solution exploits the fact that enumerations of integer-types are essentially equivalent to their underlying integer-type.

public static class BitFlag
{
    public static bool Has<T>(T val, T flag)
        where T : struct
    {
        // return ((val & flag) == flag);
#if IL
        ldarg val
        ldarg flag
        and
        ldarg flag
        ceq
        ret
#endif
        return false;
    }
    public static T SetState<T>(T val, T flag, bool on)
        where T : struct
    {
        if (on)
        {
            // return (val | flag);
#if IL
            ldarg val
            ldarg flag
            or
            ret
#endif
        }
        else
        {
            // return (val & ~flag);
#if IL
            ldarg val
            ldarg flag
            not
            and
            ret
#endif
        }
        return default(T);
    }
}

Points of Interest  

I wasn't aware of this until a few months after the original release of .NET 4.0 when I decided to go snooping to find out how they implemented the function (as the bitwise & operator does not work on two types of Enum). In most cases you don't have to be very concerned about the performance implications of using a function like Enum.HasFlag() as it is rare to be writing this sort of application in a managed language like C#.

I imagine there will come a day when you can use the type constraint where T : enum (and also where T : delegate although not relevant to this tip/article) and solution #4 will be achievable in regular C# code.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

roylawliet
Student
Canada Canada
I was born March 15th 1994 and I have been programming for about 5 years now and working with C# for about 3 years. I am experienced in a variety of programming languages such as C/C++, Delphi, VB.NET, F#, and Python. I absolutely love learning new things about computer science, so I'm always doing research and often reinventing the wheel to get a better grasp on concepts.

Comments and Discussions

 
Questionwhere T : Enum PinmemberCarlos190719-Nov-12 23:20 
Generalwhere T : enum Pinmemberspringy7616-Aug-12 0:24 
GeneralRe: where T : enum Pinmemberroylawliet16-Aug-12 2:06 
GeneralRe: where T : enum Pinmemberspringy7616-Aug-12 3:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140821.2 | Last Updated 19 Nov 2012
Article Copyright 2012 by roylawliet
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid