Enum.HasFlag, why no Enum.SetFlag?
Asked Answered
B

11

48

I have to build an extension method for each flag type I declare, like so:

public static EventMessageScope SetFlag(this EventMessageScope flags, 
    EventMessageScope flag, bool value)
{
    if (value)
        flags |= flag;
    else
        flags &= ~flag;

    return flags;
}

Why isn't there an Enum.SetFlag like there is an Enum.HasFlag?

Also, why does this not work always?

public static bool Get(this EventMessageScope flags, EventMessageScope flag)
{
    return ((flags & flag) != 0);
}

For example, if I have:

var flag = EventMessageScope.Private;

And check it like:

if(flag.Get(EventMessageScope.Public))

Where EventMessageScope.Public really is EventMessageScope.Private | EventMessageScope.PublicOnly, it returns true.

When it's not, because Private is not public, it's just half public.

The same goes for:

if(flag.Get(EventMessageScope.None))

Which returns false, except the scope is actually None (0x0), when it should always return true?

Belita answered 1/5, 2011 at 19:45 Comment(3)
@CodyGray, |= operator is shorter , but assumes familiriality with binary implementation of flags, SetFlag is much more intuitive.Legalism
I like your question and find it totally logical. I hope one day Microsoft will write a standard SetFlag. Thanks for askingYachtsman
@CodyGray Isn't 1 line instead of 4 lines (when you have a boolean and a flag as an input) every time you want to set a flag enough motivation to write an extension method? If I'd need 4 lines to add 1 to a number I'd definitely write an AddOneToInteger method.Linsang
C
3

The & operator will give you the same answer with a & b as it will with b & a, so

(EventMessaageScope.Private).Get(EventMessageScope.Private | EventMessageScope.PublicOnly)

is the same as writing

(EventMessageScope.Private | EventMessageScope.PublicOnly).Get(EventMessaageScope.Private)

If you just want to know if the value is the same as EventMessaageScope.Public, then just use equals:

EventMessageScope.Private == EventMessageScope.Public

Your method will always return false for (EventMessageScope.None).Get(EventMessaageScope.None) because None == 0 and it only returns true when the result of the AND operation is not zero. 0 & 0 == 0.

Cassandracassandre answered 1/5, 2011 at 20:7 Comment(0)
L
59

Why isn't there an Enum.SetFlag like there is an Enum.HasFlag?

HasFlag as a bitwise operation required more complicated logic and repeating the same flag twice

 myFlagsVariable=    ((myFlagsVariable & MyFlagsEnum.MyFlag) ==MyFlagsEnum.MyFlag );

so MS decided to implement it.

SetFlag and ClearFlag are concise in C#

    flags |= flag;// SetFlag

    flags &= ~flag; // ClearFlag 

but unfortunately not intuitive. Every time I need to set (or clear) a flag, I'm spending a few seconds (or minutes) to think: what is the name of the method? Why is it not shown in intellisense? Or no, I have to use bitwise operations. Note, that some developers will also ask: what is a bitwise operation?

Should SetFlag and ClearFlag extensions be created - YES to appear in intellisense.

Should SetFlag and ClearFlag extensions be used by developers - NO, because they are not efficient. UPDATE 2024: the JamesHoux’s answer explains that inlining should make it efficient.

We've created extensions in our library's class EnumFlagsHelper like in SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier, but named the function as SetFlag instead of Include and ClearFlag instead of Remove.

In the body of SetFlag methods ( and in summary comment) I decided to add

Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n 
flags |= flag;// SetFlag")

and a similar message should be added to ClearFlag

Debug.Assert( false, " do not use the extension due to performance reason, use bitwise operation with the explanatory comment instead \n 
         flags &= ~flag; // ClearFlag  ")
Legalism answered 7/7, 2012 at 11:3 Comment(7)
+1 for the advice, but would using the extension methods really have a performance impact? Maybe if you were setting many thousands of flags a second, even then, wouldn't such a simple operation get inlined during compilation anyway?Circassia
@gt: Probably performance impact is not essential, but replace the simplest binary operation with 10 lines of code including Enum.Parse doesn't look correct. And you don't know in which loop it would be used.Legalism
Ah, I assumed it would just be the short binary operations but looking at other answers I can see just what you mean. A shame C# enum handling is tricky like this, looking at Jon Skeet's UnconstrainedMelody is interesting to see similar workarounds.Circassia
Note that what you are referring to as "binary operations" are actually "bitwise operations". Binary operations are another concept, it just means there are two operands (+ is also a binary operator). There are also unary operators (e.g. ++), and ternary operators (only one so far, namely ?: as in a ? b : c). The & and | bitwise operators are both binary operators (two operands needed), while ~ is a unary operator (it just inverses the bits of its operand).Principalities
I'm also missing SetFlag, explained and implemented it here: gaevoy.com/2023/11/14/learn-enum-flags-csharp.htmlBarcellona
NO, because they are not efficient. A C++ compiler would obviously inline these, are you saying that the C# compiler wouldn't?Dowery
@ChrisW, good question, inlining is not guaranteed, even if you specify [MethodImpl(MethodImplOptions.AggressiveInlining)] See #20517603. However see the other answer, that discuss the inliningLegalism
Y
14

I've done something that works for me and that's very simple...

    public static T SetFlag<T>(this Enum value, T flag, bool set)
    {
        Type underlyingType = Enum.GetUnderlyingType(value.GetType());

        // note: AsInt mean: math integer vs enum (not the c# int type)
        dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
        dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
        if (set)
        {
            valueAsInt |= flagAsInt;
        }
        else
        {
            valueAsInt &= ~flagAsInt;
        }

        return (T)valueAsInt;
    }

Usage:

    var fa = FileAttributes.Normal;
    fa = fa.SetFlag(FileAttributes.Hidden, true);
Yachtsman answered 5/2, 2014 at 15:31 Comment(0)
M
11
public static class SomeEnumHelperMethodsThatMakeDoingWhatYouWantEasier
{
    public static T IncludeAll<T>(this Enum value)
    {
        Type type = value.GetType();
        object result = value;
        string[] names = Enum.GetNames(type);
        foreach (var name in names)
        {
            ((Enum) result).Include(Enum.Parse(type, name));
        }

        return (T) result;
        //Enum.Parse(type, result.ToString());
    }

    /// <summary>
    /// Includes an enumerated type and returns the new value
    /// </summary>
    public static T Include<T>(this Enum value, T append)
    {
        Type type = value.GetType();

        //determine the values
        object result = value;
        var parsed = new _Value(append, type);
        if (parsed.Signed is long)
        {
            result = Convert.ToInt64(value) | (long) parsed.Signed;
        }
        else if (parsed.Unsigned is ulong)
        {
            result = Convert.ToUInt64(value) | (ulong) parsed.Unsigned;
        }

        //return the final value
        return (T) Enum.Parse(type, result.ToString());
    }

    /// <summary>
    /// Check to see if a flags enumeration has a specific flag set.
    /// </summary>
    /// <param name="variable">Flags enumeration to check</param>
    /// <param name="value">Flag to check for</param>
    /// <returns></returns>
    public static bool HasFlag(this Enum variable, Enum value)
    {
        if (variable == null)
            return false;

        if (value == null)
            throw new ArgumentNullException("value");

        // Not as good as the .NET 4 version of this function, 
        // but should be good enough
        if (!Enum.IsDefined(variable.GetType(), value))
        {
            throw new ArgumentException(string.Format(
                "Enumeration type mismatch.  The flag is of type '{0}', " +
                "was expecting '{1}'.", value.GetType(), 
                variable.GetType()));
        }

        ulong num = Convert.ToUInt64(value);
        return ((Convert.ToUInt64(variable) & num) == num);
    }


    /// <summary>
    /// Removes an enumerated type and returns the new value
    /// </summary>
    public static T Remove<T>(this Enum value, T remove)
    {
        Type type = value.GetType();

        //determine the values
        object result = value;
        var parsed = new _Value(remove, type);
        if (parsed.Signed is long)
        {
            result = Convert.ToInt64(value) & ~(long) parsed.Signed;
        }
        else if (parsed.Unsigned is ulong)
        {
            result = Convert.ToUInt64(value) & ~(ulong) parsed.Unsigned;
        }

        //return the final value
        return (T) Enum.Parse(type, result.ToString());
    }

    //class to simplfy narrowing values between
    //a ulong and long since either value should
    //cover any lesser value
    private class _Value
    {
        //cached comparisons for tye to use
        private static readonly Type _UInt32 = typeof (long);
        private static readonly Type _UInt64 = typeof (ulong);

        public readonly long? Signed;
        public readonly ulong? Unsigned;

        public _Value(object value, Type type)
        {
            //make sure it is even an enum to work with
            if (!type.IsEnum)
            {
                throw new ArgumentException(
                    "Value provided is not an enumerated type!");
            }

            //then check for the enumerated value
            Type compare = Enum.GetUnderlyingType(type);

            //if this is an unsigned long then the only
            //value that can hold it would be a ulong
            if (compare.Equals(_UInt32) || compare.Equals(_UInt64))
            {
                Unsigned = Convert.ToUInt64(value);
            }
                //otherwise, a long should cover anything else
            else
            {
                Signed = Convert.ToInt64(value);
            }
        }
    }
}
Maroc answered 1/5, 2011 at 20:28 Comment(3)
@smartcaveman, why in SetFlag you are checking for null? Afaik, enums are not nullableLegalism
@MichaelFreidgeim, an actual enum Type is not nullable, however Enum is a boxed reference type representation. Since the parameter is a reference type, it can be null. (You can have this same issue when you are using the ValueType or Object base classes to represent a value type).Maroc
This is really nice, but there are errors. In the first method, IncludeAll, the accumulator "result" is not accumulating because the call to "Include" is not being set back into the "result" var. I'll edit the code to fix this problem.Borreri
S
5

It's 2021 and C# has a lot of nice features that mean there SHOULD be a much more elegant way of doing this. Let's discuss the claims of previous answers...

CLAIM 1: Turning a flag off is inefficient because it uses two ops and invoking another method just adds more overhead.

THIS SHOULD BE FALSE. If you add the AggressiveInlining compiler flag, the compiler SHOULD hoist the bitwise operation to a direct inline operation. If you are writing critical code, you may want to benchmark this to confirm since results can vary even between minor compiler versions. But the point is, you should be able to invoke a convenience method WITHOUT paying a method lookup cost.

CLAIM 2: Its excessively verbose because you have to set the flag and then assign the return value.

THIS ALSO SHOULD BE FALSE. C# offers 'ref' which allows you to directly manipulate a value-type parameter by reference (in this case your enum). Combined with AggressiveInlining, the compiler should be smart enough to completely remove the ref pointers and the generated IL should look the same as if you directly inlined two bitwise operations.

CAVEATS: Of course, this is all theory. Perhaps someone else can come along in the comments here and examine the IL from proposed code below. I don't have enough experience looking at IL myself (nor the time right now) to see if the hypothetical claims are true. But I figured this answer is still worth posting because the fact is that C# should be capable of doing what I'm explaining.

If someone else can confirm this, I can update the answer accordingly.

public enum MyCustomEnum : long
{
    NO_FLAGS            = 0,
    SOME_FLAG           = 1,
    OTHER_FLAG          = 1 << 1,
    YET_ANOTHER_FLAG    = 1 << 2,
    ANOTHER STILL       = 1 << 3
}

public static class MyCustomEnumExt
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void TurnOFF(ref this MyCustomEnum status, MyCustomEnum flag)
        => status &= ~flag;

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void TurnON(ref this MyCustomEnum status, MyCustomEnum flag)
        => status |= flag;
}

You should be able to use the code like this:

//Notice you don't have to return a value from the extension methods to assign manually.
MyCustomEnum mc = MyCustomEnum.SOME_FLAG;
mc.TurnOFF(MyCustomEnum.SOME_FLAG);
mc.TurnON(MyCustomEnum.OTHER_FLAG);

Even if the compiler fails to optimize this correctly, it is still extremely handy. At the very least you can use it in non-critical code and expect excellent readability.

Streetman answered 11/6, 2021 at 23:8 Comment(0)
C
3

The & operator will give you the same answer with a & b as it will with b & a, so

(EventMessaageScope.Private).Get(EventMessageScope.Private | EventMessageScope.PublicOnly)

is the same as writing

(EventMessageScope.Private | EventMessageScope.PublicOnly).Get(EventMessaageScope.Private)

If you just want to know if the value is the same as EventMessaageScope.Public, then just use equals:

EventMessageScope.Private == EventMessageScope.Public

Your method will always return false for (EventMessageScope.None).Get(EventMessaageScope.None) because None == 0 and it only returns true when the result of the AND operation is not zero. 0 & 0 == 0.

Cassandracassandre answered 1/5, 2011 at 20:7 Comment(0)
H
3

Here is another quick and dirty way to SetFlag for any Enum:

public static T SetFlag<T>(this T flags, T flag, bool value) where T : struct, IComparable, IFormattable, IConvertible
    {
        int flagsInt = flags.ToInt32(NumberFormatInfo.CurrentInfo);
        int flagInt = flag.ToInt32(NumberFormatInfo.CurrentInfo);
        if (value)
        {
            flagsInt |= flagInt;
        }
        else
        {
            flagsInt &= ~flagInt;
        }
        return (T)(Object)flagsInt;
    }
Handiness answered 3/4, 2017 at 10:24 Comment(1)
This only works for Enums of the default underlying type (int32). If you declare e.g. enum as ulong then we get invalid cast errors. This answer is faster for default types, Eric Ouellet's heavier answer with dynamic casting handles unknown enum types.Kirghiz
A
2

Enums got borked by the C language a long time ago. Having a modicum of type safety in the C# language was important to the designers, leaving no room for an Enum.SetFlags when the underlying type can be anything between a byte and a long. Another C induced problem btw.

The proper way to deal with it is to write this kind of code inline explicitly and not try to shove it into an extension method. You don't want to write a C macro in the C# language.

Altdorfer answered 1/5, 2011 at 19:45 Comment(6)
Why is it a C-induced problem that an enum can be any type from a byte to a long? That seems like a convenient feature of any language.Tholos
I don't understand your answer. Set flag should take one or more flag(s) and a Boolean where true is flag(s) set and false unset. Where is the connection with C or the underlying type ???Yachtsman
The essence of type safety is that you never write an incorrect number of bytes into a variable. A .NET enum can be 1, 2, 4 or 8 bytes. Writing an extension method therefore becomes difficult, it has to write the correct number of bytes for any enum type. It can only do this by using Reflection, the only way to figure out how many bytes are used by the enum type. Which makes writing an enum an easy two orders of magnitude more expensive.Altdorfer
Thanks a lot. I 'm trying to write a SetFlag now and see what your are talking about!Yachtsman
@Hans, I appreciate your thought. I've posted a way to do it. I wonder if you can tell me what you think about it? It's not perfect but it works in my case (and support long, int, etc). What flaws do you see?Yachtsman
Reflection is not the only way to determine the size of the type. At least, not anymore. You can use sizeof(Type) and solve the type safe value change in unsafe code, see this solution: https://mcmap.net/q/353339/-enum-hasflag-why-no-enum-setflagUnintelligent
P
2

To answer part of your your question: the Get function works properly according to binary logic - it checks for any match. If you want to match the whole set of flags, consider this instead:

return ((flags & flag) != flag);

Regarding "why isn't there SetFlag"... probably because it's not really needed. Flags are integers. There is already a convention for dealing with those and it applies to flags as well. If you don't want to write it with | and & - that's what the custom static addons are for - you can just use your own functions as you demonstrated yourself :)

Pettitoes answered 1/5, 2011 at 20:3 Comment(8)
you could say the same about HasFlag... yet it existsBelita
@Nico True - but look at the comment on msdn: "Efficiency warning A user of this method should be aware that the current implementation is painfully slow, approximately 1000 times slower than manually inlining the code that the method is defined to expand into, and therefore use in performance critical code is not recommended." Why the inconsistency itself is there is probably a question for MS guys, not SO :(Pettitoes
Wow. 1000 times slower to get into the method, execute it and return? That's some perspective.Stewart
Holy chocolate, link for that? That's awful!Belita
@Nico msdn.microsoft.com/en-us/library/system.enum.hasflag.aspx#2 - I'd imagine that & involves only a binary operation. HasFlag involves copy values to stack, enter, binary op, copy the result, return, assignment from stack. (the link is a community comment, so YMMV and all that)Pettitoes
@Bolt: The reason that Enum.HasFlag is slower is because it uses reflection to do some additional type-checking. The problem is not the fact that it's a separate method call; that's certainly not anywhere near enough to explain the overhead you're seeing. Since I still target .NET 3.5 (which doesn't have a handy HasFlag method), I've written my own that doesn't use reflection (at least, not in Release builds), and I see performance on-par with the above code written in-line. (I wasted more time than Donald Knuth would approve benchmarking it.)Tholos
FYI, @Nico & viraptor, I'm not seeing this in the current docs so maybe we have a different implementation? But I haven't done an decompile to compare. (shrug)Manolete
@Manolete This has since been optimized. The JIT has been taught to recognize when the enum type is known at compile time and translates it into a bit test.Tessi
U
1

Good answers so far but in case you are looking for a more performant shorthand that doesn't allocate managed memory, you can use this:

using System;
using System.Runtime.CompilerServices;
public static class EnumFlagExtensions
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TEnum AddFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            switch (sizeof(TEnum))
            {
                case 1:
                    {
                        var r = *(byte*)(&lhs) | *(byte*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 2:
                    {
                        var r = *(ushort*)(&lhs) | *(ushort*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 4:
                    {
                        var r = *(uint*)(&lhs) | *(uint*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 8:
                    {
                        var r = *(ulong*)(&lhs) | *(ulong*)(&rhs);
                        return *(TEnum*)&r;
                    }
                default:
                    throw new Exception("Size does not match a known Enum backing type.");
            }
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static TEnum RemoveFlag<TEnum>(this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            switch (sizeof(TEnum))
            {
                case 1:
                    {
                        var r = *(byte*)(&lhs) & ~*(byte*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 2:
                    {
                        var r = *(ushort*)(&lhs) & ~*(ushort*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 4:
                    {
                        var r = *(uint*)(&lhs) & ~*(uint*)(&rhs);
                        return *(TEnum*)&r;
                    }
                case 8:
                    {
                        var r = *(ulong*)(&lhs) & ~*(ulong*)(&rhs);
                        return *(TEnum*)&r;
                    }
                default:
                    throw new Exception("Size does not match a known Enum backing type.");
            }
        }
 
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void SetFlag<TEnum>(ref this TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            fixed (TEnum* lhs1 = &lhs)
            {
                switch (sizeof(TEnum))
                {
                    case 1:
                        {
                            var r = *(byte*)(lhs1) | *(byte*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 2:
                        {
                            var r = *(ushort*)(lhs1) | *(ushort*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 4:
                        {
                            var r = *(uint*)(lhs1) | *(uint*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 8:
                        {
                            var r = *(ulong*)(lhs1) | *(ulong*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    default:
                        throw new Exception("Size does not match a known Enum backing type.");
                }
            }
        }
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClearFlag<TEnum>(this ref TEnum lhs, TEnum rhs) where TEnum : unmanaged, Enum
    {
        unsafe
        {
            fixed (TEnum* lhs1 = &lhs)
            {
                switch (sizeof(TEnum))
                {
                    case 1:
                        {
                            var r = *(byte*)(lhs1) & ~*(byte*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 2:
                        {
                            var r = *(ushort*)(lhs1) & ~*(ushort*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 4:
                        {
                            var r = *(uint*)(lhs1) & ~*(uint*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    case 8:
                        {
                            var r = *(ulong*)(lhs1) & ~*(ulong*)(&rhs);
                            *lhs1 = *(TEnum*)&r;
                            return;
                        }
                    default:
                        throw new Exception("Size does not match a known Enum backing type.");
                }
            }
        }
    }
}

It just requires C# 7.3 or higher and the compiler to be instructed to accept /unsafe code.

AddFlag & RemoveFlag do not modify the enum value you call this on, SetFlag and ClearFlag do modify it. This is likely the generic solution to this that has the lowest performance overhead but it still won't be as fast as just directly using

flags |= flag;
flags &= ~flag;
Unintelligent answered 28/6, 2021 at 7:57 Comment(0)
M
0

Here's an improved generic way of @Martin Tilo Schmitz's function, which doesn't require you to set /unsafe in compiler settings by making use of System.Runtime.CompilerServices.Unsafe

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetFlag<T>(this ref T @enum, T flag) where T : unmanaged, Enum
{
    if (Unsafe.SizeOf<T>() == 4) // match default enum size first
        Unsafe.As<T, uint>(ref @enum) |= Unsafe.As<T, uint>(ref flag);
    else if (Unsafe.SizeOf<T>() == 8) // enum * : long
        Unsafe.As<T, ulong>(ref @enum) |= Unsafe.As<T, ulong>(ref flag);
    else if (Unsafe.SizeOf<T>() == 1)
        Unsafe.As<T, byte>(ref @enum) |= Unsafe.As<T, byte>(ref flag);
    else if (Unsafe.SizeOf<T>() == 2)
        Unsafe.As<T, short>(ref @enum) |= Unsafe.As<T, short>(ref flag);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void UnsetFlag<T>(this ref T @enum, T flag) where T : unmanaged, Enum
{
    if (Unsafe.SizeOf<T>() == 4) // match default enum size first
        Unsafe.As<T, uint>(ref @enum) &= ~Unsafe.As<T, uint>(ref flag);
    else if (Unsafe.SizeOf<T>() == 8) // enum * : long
        Unsafe.As<T, ulong>(ref @enum) &= ~Unsafe.As<T, ulong>(ref flag);
    else if (Unsafe.SizeOf<T>() == 1)
        Unsafe.As<T, byte>(ref @enum) &= (byte)~Unsafe.As<T, byte>(ref flag);
    else if (Unsafe.SizeOf<T>() == 2)
        Unsafe.As<T, short>(ref @enum) &= (short)~Unsafe.As<T, short>(ref flag);
}
Millimicron answered 19/9, 2023 at 11:19 Comment(0)
R
-3

The reason I'm finding is that since enum is a value type, you cannot pass it in and set its type. To all of you that think its stupid, I say this to you: Not all developers understand bit flags and how to turn them on or off (which is much less intuitive).

Not a stupid idea, just not possible.

Rummel answered 2/6, 2012 at 22:40 Comment(1)
Ken, it's possible, see answer above https://mcmap.net/q/353339/-enum-hasflag-why-no-enum-setflag ./smartcaveman just uses Include instead of SetFlagLegalism

© 2022 - 2024 — McMap. All rights reserved.