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?

Razzie
  • 30,834
  • 11
  • 63
  • 78
Enyra
  • 17,542
  • 12
  • 35
  • 44
  • An almost similar question can be found here: http://stackoverflow.com/questions/746905/get-integral-value-of-boxed-enum – Kirtan Jul 06 '09 at 12:12
  • Yet another reason why C# needs a generic enum constraint: http://stackoverflow.com/questions/7244 – Keith Jul 28 '09 at 08:23

7 Answers7

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;
}
Noldorin
  • 144,213
  • 56
  • 264
  • 302
arbiter
  • 9,447
  • 1
  • 32
  • 43
  • 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. – Noldorin Jul 06 '09 at 12:34
  • @Noldorin, of course this is pretty basic, even trivial, that is why so many answers here :) – arbiter Jul 06 '09 at 12:37
  • @tvanfosson: It's shorter to leave out the variable declaration, any no less readable, in my opinion. This is however pedantry and semantics... – Noldorin Jul 06 '09 at 12:41
  • I tried this one with two enums with mulitiple values and it didn't work. – Jowen Nov 14 '14 at 10:19
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

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • [+1] I was looking for a way to enumerate over set flags; my code uses same technique behind your GetFlags 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. – one.beat.consumer Dec 01 '11 at 18:38
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
    }
}
Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • Not correct statement, because flags can have more than one bit set. – arbiter Jul 06 '09 at 12:18
  • Char isnt a valid underlying type ... in C#. His question does not state who created the enum, and in what language. – sisve Jul 06 '09 at 12:24
  • 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. – tvanfosson Jul 06 '09 at 12:29
  • 1
    @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. – Noldorin Jul 06 '09 at 12:30
  • @tvanfosson: You're right. That was me being silly and editing the code directly online instead of in Visual Studio. ;) – Noldorin Jul 06 '09 at 12:31
  • @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). – Noldorin Jul 06 '09 at 12:32
  • @Simon -- actually the question title specifies C#. – tvanfosson Jul 06 '09 at 12:35
  • Indeed, but I edited my description anyway to point out that it is valid CIL, just for completeness. – Noldorin Jul 06 '09 at 12:36
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...
hugoware
  • 35,731
  • 24
  • 60
  • 70
3

Simply use the Enum.HasFlag() Method !

Shazam
  • 31
  • 1
  • Yes now with .net4.0 ;) but have to use it carefully, cauze it is very slow. – Enyra Nov 02 '10 at 13:59
  • 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 method helps us do this. – one.beat.consumer Dec 01 '11 at 18:39
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.

David Basarab
  • 72,212
  • 42
  • 129
  • 156
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); }

Rik
  • 1