Wednesday, March 5th, 2008
Checking Flags in C# Enums
I like C# enums and I also like using them as bitfields, even though apparently not everyone does. I realize they aren’t perfectly typesafe, but then I don’t think that’s the problem Abrams and co. were trying to solve anyway.
Here’s one:
[Flags]
public enum Fruits
{
Apple = 1,
Banana = 2,
Cherry = 4,
Date = 8,
Eggplant = 16
}
Nice, clean syntax. The way they solved C++’s name collision issues with enum values is genius: Fruits.Apple. Clearly these guys are using the old noggin.
The Annoying Bit (Argh, a Pun)
The one thing that does annoy me about flag enums is the syntax to see if a given flag (or set of flags) is set:
if ((myFruit & Fruits.Date) == Fruits.Date)
I’m not afraid of bitwise operators, but there’s some serious lameness in here. Needing to specify the explicit == for type safety and having to use the parenthesis because the operator precedence puts == before & first? Gross.
For Every Nail There is a Hammer
Behold the solution:
public static class FruitsExtensions
{
public static bool IsSet(this Fruits fruits, Fruits flags)
{
return (fruits & flags) == flags;
}
}
With that, you can just do:
if (myFruit.IsSet(Fruits.Date))
Much nicer. For kicks, here’s some other useful methods:
public static class FruitsExtensions
{
public static bool IsSet(this Fruits fruits, Fruits flags)
{
return (fruits & flags) == flags;
}
public static bool IsNotSet(this Fruits fruits, Fruits flags)
{
return (fruits & (~flags)) == 0;
}
public static Fruits Set(this Fruits fruits, Fruits flags)
{
return fruits | flags;
}
public static Fruits Clear(this Fruits fruits, Fruits flags)
{
return fruits & (~flags);
}
}
Useful, no?
Why Solve 1 When You Can Solve n?
So, if you’re like me and this guy, right now you’re thinking, “This just fixes one enum. Can I solve it for all enums?” Ideally, you’d do something like:
public static class EnumExtensions
{
public static bool IsSet<T>(this T value, T flags) where T : Enum
{
return (value & flags) == flags;
}
}
Unfortunately, that doesn’t fly. You can’t use Enum as a constraint. Likewise, there’s no way to require a typeparam to implement an operator (like “&” above). You can implement a generic solution for this:
public static class EnumExtensions
{
public static bool IsSet<T>(this T value, T flags) where T : struct
{
Type type = typeof(T);
// only works with enums
if (!type.IsEnum) throw new ArgumentException(
"The type parameter T must be an enum type.");
// handle each underlying type
Type numberType = Enum.GetUnderlyingType(type);
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 + ".");
}
}
///
/// Helper function for handling the value types. Boxes the params to
/// object so that the cast can be called on them.
///
private static bool BoxUnbox<T>(object value, object flags, Func<T, T, bool> op)
{
return op((T)value, (T)flags);
}
}
…but, yeah, not exactly fun using reflection for this.
March 5th, 2008 at 11:34 am
if ((myFruit & Fruits.Date) != 0) is shorter.
March 5th, 2008 at 8:35 pm
Yes, it’s shorter in that case, but that isn’t always correct:
Fruits myFruit = Fruits.Apple | Fruits.Banana;
Fruits flagsToCheck = Fruits.Banana | Fruits.Cherry;
if (myFruit & flagsToCheck != 0)
{
// will incorrectly get here
// Fruits.Cherry was not set in myFruit
}
Comparing it != 0 will return true if any flag is set, and we only want it true if all flags are set. It’s an easy mistake to make, which is another reason why it makes sense to try to abstract out the bitwise operators.
March 7th, 2008 at 4:22 am
Yes, that cant work with combined values :)
October 2nd, 2008 at 12:22 pm
If you use C++ w/ CLR you can implement this, as you can use Enum as a generic type constraint in C++/CLI. In fact I have written this exact class using extension methods in c++. (To define an extension method in c++/cli you define a class as “abstract sealed”, add an [Extension] attribute to both the class and the static methods).