Comparing enum flags in C#
Asked Answered
A

7

15

I need to detect if a flag is set within an enum value, which type is marked with the Flag attribute.

Usually it is made like that:

(value & flag) == flag

But since I need to do this by generic (sometimes at runtime I event have only an "Enum" reference. I can not find an easy way to use the & operator. At the moment I make it like this:

    public static bool IsSet<T>(this T value, T flags) where T : Enum
    { 
        Type numberType = Enum.GetUnderlyingType(typeof(T));

        if (numberType.Equals(typeof(int)))
        {
            return BoxUnbox<int>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(sbyte)))
        {
            return BoxUnbox<sbyte>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(byte)))
        {
            return BoxUnbox<byte>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(short)))
        {
            return BoxUnbox<short>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(ushort)))
        {
            return BoxUnbox<ushort>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(uint)))
        {
            return BoxUnbox<uint>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(long)))
        {
            return BoxUnbox<long>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(ulong)))
        {
            return BoxUnbox<ulong>(value, flags, (a, b) => (a & b) == b);
        }
        else if (numberType.Equals(typeof(char)))
        {
            return BoxUnbox<char>(value, flags, (a, b) => (a & b) == b);
        }
        else
        {
            throw new ArgumentException("Unknown enum underlying type " + numberType.Name + ".");
        }
    }


    private static bool BoxUnbox<T>(object value, object flags, Func<T, T, bool> op)
    {
        return op((T)value, (T)flags);
    }

But I don't like the never ending if - else blocks, so is there a way to cast these values that I can use the & operator or any other solution to check this?

Augustinaaugustine answered 6/7, 2009 at 12:3 Comment(2)
An almost similar question can be found here: #747405Kiyohara
Yet another reason why C# needs a generic enum constraint: stackoverflow.com/questions/7244Santee
B
6

Personally, I think that look fine because you've wrapped it into a single purpose function. If you had that code scattered through an entire program I think you would have some problems, but what you've created improves clarity everywhere it is used and the function itself is clear enough what it does.

Just my opinion of course.

You could though, use the is keyword, which might help a little

public static bool IsSet<T>(this T value, T flags) where T : Enum
{ 
    if (value is int)
    {
        return ((int)(object)a & (int)(object)b) == (int)(object)b);
    }
    //etc...
Babu answered 6/7, 2009 at 12:13 Comment(3)
FWIW, you can't cast a generic type directly to a value type, thus the additional object casting.Babu
Yep, my fault. I forget that this is why I've used Convert class instead of direct casting :)Arand
I keep it like I implemented it first.Augustinaaugustine
A
15

For me it looks overcomplicated. How about this (keeping in mind that enum is always mapped to an integer value type):

public static bool IsSet<T>(T value, T flags) where T : struct
{
    // You can add enum type checking to be perfectly sure that T is enum, this have some cost however
    // if (!typeof(T).IsEnum)
    //     throw new ArgumentException();
    long longFlags = Convert.ToInt64(flags);
    return (Convert.ToInt64(value) & longFlags) == longFlags;
}
Arand answered 6/7, 2009 at 12:17 Comment(4)
Yeah, so this is basically the same as mine now. Not sure I would bother factoring out the Convert.ToInt64(flags) anyway, as the C# compiler would optimise it away anyway.Neoplasm
@Noldorin, of course this is pretty basic, even trivial, that is why so many answers here :)Arand
@tvanfosson: It's shorter to leave out the variable declaration, any no less readable, in my opinion. This is however pedantry and semantics...Neoplasm
I tried this one with two enums with mulitiple values and it didn't work.Setser
C
10

I wrote a set of extension methods for enums, in case you need it :

public static class EnumExtensions
{
    private static void CheckEnumWithFlags<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (!Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckEnumWithFlags<T>();
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckEnumWithFlags<T>();
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }
}

The main drawback is that you can't specify where T : Enum : it is explicitly forbidden ("Constraint cannot be special class 'System.Enum'"), so the extension methods will appear in intellisense for all structs... I added the CheckEnumWithFlags method to check that the type is actually an enum, and has the Flags attribute.


UPDATE : Jon Skeet recently started an interesting library called UnconstrainedMelody which does exactly the same sort of things, and works around the generic type constraint limitation mentioned above

Censer answered 6/7, 2009 at 12:36 Comment(1)
[+1] I was looking for a way to enumerate over set flags; my code uses same technique behind your GetFlags<T> method only it's not an extension. It's sad that C# cant let specify "where T : Enum". Without the extension method you can simply use HasFlag() which is built in.Shagbark
N
7

This should do the job for enum types with any underlying types:

public static bool IsSet<T>(this T value, T flags) where T : struct
{
    return (Convert.ToInt64(value) & Convert.ToInt64(flags)) ==
        Convert.ToInt64(flags);
}

Convert.ToInt64 is used because a 64-bit integer is the "widest" integral type possible, to which all enum values can be cast (even ulong). Note that char is not a valid underlying type. It seems that it is not valid in C#, but it is in general valid in CIL/for the CLR.

Also, you can't enforce a generic type constraint for enums (i.e. where T : struct); the best you can do is use where T : struct to enforce T to be a value type, and then optionally perform a dynamic check to ensure that T is an enum type.

For completeness, here is my very brief test harness:

static class Program
{
    static void Main(string[] args)
    {
        Debug.Assert(Foo.abc.IsSet(Foo.abc));
        Debug.Assert(Bar.def.IsSet(Bar.def));
        Debug.Assert(Baz.ghi.IsSet(Baz.ghi));
    }

    enum Foo : int
    {
        abc = 1,
        def = 10,
        ghi = 100
    }

    enum Bar : sbyte
    {
        abc = 1,
        def = 10,
        ghi = 100
    }

    enum Baz : ulong
    {
        abc = 1,
        def = 10,
        ghi = 100
    }
}
Neoplasm answered 6/7, 2009 at 12:16 Comment(8)
Not correct statement, because flags can have more than one bit set.Arand
Char isnt a valid underlying type ... in C#. His question does not state who created the enum, and in what language.Combination
I removed my original comment, but I'm still not convinced this is absolutely correct since the right hand side of the test is still an Enum. It needs to be converted as well, I believe.Lacroix
@arbiter: It's perfectly correct as long as you only test for one flag. Indeed, checking for "== flags" works in the more general case of combinations of flags.Neoplasm
@tvanfosson: You're right. That was me being silly and editing the code directly online instead of in Visual Studio. ;)Neoplasm
@Simon: I wasn't aware of that, but I'll take your word that it's valid as an underlying type in general (i.e. in CIL).Neoplasm
@Simon -- actually the question title specifies C#.Lacroix
Indeed, but I edited my description anyway to point out that it is valid CIL, just for completeness.Neoplasm
B
6

Personally, I think that look fine because you've wrapped it into a single purpose function. If you had that code scattered through an entire program I think you would have some problems, but what you've created improves clarity everywhere it is used and the function itself is clear enough what it does.

Just my opinion of course.

You could though, use the is keyword, which might help a little

public static bool IsSet<T>(this T value, T flags) where T : Enum
{ 
    if (value is int)
    {
        return ((int)(object)a & (int)(object)b) == (int)(object)b);
    }
    //etc...
Babu answered 6/7, 2009 at 12:13 Comment(3)
FWIW, you can't cast a generic type directly to a value type, thus the additional object casting.Babu
Yep, my fault. I forget that this is why I've used Convert class instead of direct casting :)Arand
I keep it like I implemented it first.Augustinaaugustine
C
3

Simply use the Enum.HasFlag() Method !

Cleek answered 12/8, 2010 at 10:16 Comment(2)
Yes now with .net4.0 ;) but have to use it carefully, cauze it is very slow.Augustinaaugustine
The issue is when you are comparing a set of flags to another set of flags... if you just use the operands, you can find out if all flags match, or if a particular flag is set (HasFlag does this too)... but it gets tricky when you only care if one of the flag values is present in the other set of flag values... Thomas's GetFlags<T> method helps us do this.Shagbark
C
0

I have used this to compare flags

public static bool IsSet<T>(this T input, T match)
{
    return (Convert.ToUInt32(input) & Convert.ToUInt32(match)) != 0;
}

Here you can do the different conversions. From int to short to long.

Contrapuntist answered 6/7, 2009 at 12:16 Comment(0)
S
0

or... public static bool IsSet(this Enum value, Enum compare) { int baseValue = value.ToInt32(); int compareValue = compare.ToInt32(); if (baseValue == 0) return false; return ((baseValue & compareValue) == compareValue); }

Sorenson answered 28/7, 2009 at 8:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.