39

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?

casperOne
  • 70,959
  • 17
  • 175
  • 239
bevacqua
  • 43,414
  • 51
  • 157
  • 277
  • 10
    It seems completely nonsensical to define an extension method to *set* a flag. The whole point is you can set flags with the simple `enumValue |= flag;` syntax. It's unclear why that's so unintuitive you have to abstract it out into an extension method. Do you have an `AddOneToInteger` method, too? – Cody Gray May 02 '11 at 06:45
  • 12
    @CodyGray, |= operator is shorter , but assumes familiriality with binary implementation of flags, SetFlag is much more intuitive. – Michael Freidgeim Jun 23 '12 at 01:56
  • 2
    I like your question and find it totally logical. I hope one day Microsoft will write a standard SetFlag. Thanks for asking – Eric Ouellet Feb 05 '14 at 16:04
  • 6
    @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. – Juan Dec 05 '14 at 09:17

8 Answers8

49

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.

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  ")
Community
  • 1
  • 1
Michael Freidgeim
  • 21,559
  • 15
  • 127
  • 153
  • +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? – g t Jun 02 '16 at 05:58
  • 1
    @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. – Michael Freidgeim Jun 02 '16 at 07:55
  • 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. – g t Jun 02 '16 at 08:13
  • 4
    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). – MarioDS Jul 06 '16 at 07:20
12

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);
Eric Ouellet
  • 9,516
  • 8
  • 69
  • 100
10
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);
            }
        }
    }
}
casperOne
  • 70,959
  • 17
  • 175
  • 239
smartcaveman
  • 38,142
  • 26
  • 119
  • 203
  • @smartcaveman, why in SetFlag you are checking for null? Afaik, enums are not nullable – Michael Freidgeim Jun 23 '12 at 02:06
  • @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). – smartcaveman Jun 23 '12 at 02:52
  • 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. – argyle Dec 19 '16 at 20:28
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;
    }
Community
  • 1
  • 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. – Etherman Mar 08 '19 at 15:04
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.

Mark Cidade
  • 94,042
  • 31
  • 216
  • 230
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 :)

viraptor
  • 30,857
  • 7
  • 96
  • 176
  • 4
    you could say the same about HasFlag... yet it exists – bevacqua May 01 '11 at 20:16
  • @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 :( – viraptor May 01 '11 at 20:19
  • Wow. 1000 times slower to get into the method, execute it and return? That's some perspective. – BoltClock May 01 '11 at 20:32
  • Holy chocolate, link for that? That's awful! – bevacqua May 01 '11 at 20:36
  • @Nico http://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) – viraptor May 01 '11 at 20:40
  • @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.) – Cody Gray May 02 '11 at 06:43
  • 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) – FLGMwt Nov 08 '14 at 00:33
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.

Hans Passant
  • 873,011
  • 131
  • 1,552
  • 2,371
  • 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. – Cody Gray May 02 '11 at 06:47
  • 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 ??? – Eric Ouellet Feb 05 '14 at 13:56
  • 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. – Hans Passant Feb 05 '14 at 14:05
  • Thanks a lot. I 'm trying to write a SetFlag now and see what your are talking about! – Eric Ouellet Feb 05 '14 at 14:30
  • @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? – Eric Ouellet Feb 05 '14 at 15:35
-2

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.

Ken
  • 1