Enumeration Extensions 2.0
A while back I had posted some code about making it easier to work with enumerated types — Especially with the Flags
attribute. An experienced programmer won’t have a hard time understanding all the voodoo magic behind the the bitwise operators but for the rest of us then something a little easier to read is always welcome.
The problem with the previous code is that it was created to work with the project I was currently working on so I didn’t put a lot of effort into making it work with multiple types. That said, below is the official version 2.0 of the EnumerationExtensions
class.
using System; namespace Extensions { /// <summary> /// Extension methods to make working with Enum values easier /// </summary> public static class EnumerationExtensions { #region Extension Methods /// <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; _Value 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> /// 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; _Value 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()); } /// <summary> /// Checks if an enumerated type contains a value /// </summary> public static bool Has<T>(this Enum value, T check) { Type type = value.GetType(); //determine the values object result = value; _Value parsed = new _Value(check, type); if (parsed.Signed is long) { return (Convert.ToInt64(value)& (long)parsed.Signed) == (long)parsed.Signed; } else if (parsed.Unsigned is ulong) { return (Convert.ToUInt64(value) & (ulong)parsed.Unsigned) == (ulong)parsed.Unsigned; } else { return false; } } /// <summary> /// Checks if an enumerated type is missing a value /// </summary> public static bool Missing<T>(this Enum obj, T value) { return !EnumerationExtensions.Has<T>(obj, value); } #endregion #region Helper Classes //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 Type _UInt64 = typeof(ulong); private static Type _UInt32 = typeof(long); public long? Signed; public 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(_Value._UInt32) || compare.Equals(_Value._UInt64)) { this.Unsigned = Convert.ToUInt64(value); } //otherwise, a long should cover anything else else { this.Signed = Convert.ToInt64(value); } } } #endregion } }
This code results in a much easier to read syntax and is mildly better at avoiding type casting issues. Instead of defaulting to int
as the other version did, this version attempts to decide between Int64
or UInt64
since either could meet the requirements for any of their lesser counterparts.
Now we can use syntax similar like you see below…
//create the typical object RegexOptions options = RegexOptions.None; //Assign a value options = options.Include(RegexOptions.IgnoreCase); //IgnoreCase //Or assign multiple values options = options.Include(RegexOptions.Multiline | RegexOptions.Singleline); //IgnoreCase, Multiline, Singleline //Remove values from the list options = options.Remove(RegexOptions.IgnoreCase); //Multiline, Singleline //Check if a value even exists bool multiline = options.Has(RegexOptions.Multiline); //true bool ignoreCase = options.Missing(RegexOptions.IgnoreCase); //true
Anyways, a whole lot easier to read in my opinion. Enjoy!
This type of thing makes code so much easier to read and maintain. Thanks!
Visual C# Kicks
March 19, 2010 at 2:16 pm
Very nice. One issue with it though.
When I create a bunch of permissions and I remove one, the value returned from the remove method is the one I removed.
This fails at the Assert.IsFalse check.
var options = PermissionRepository.PermissionRoles.None;
options = options.Include(PermissionRepository.PermissionRoles.Create);
options = options.Include(PermissionRepository.PermissionRoles.Modify);
options = options.Include(PermissionRepository.PermissionRoles.None);
options = options.Include(PermissionRepository.PermissionRoles.Delete);
Assert.IsTrue(options.Has(PermissionRepository.PermissionRoles.Create));
Assert.IsTrue(options.Has(PermissionRepository.PermissionRoles.Modify));
Assert.IsTrue(options.Has(PermissionRepository.PermissionRoles.None));
Assert.IsTrue(options.Has(PermissionRepository.PermissionRoles.Delete));
options = options.Remove(PermissionRepository.PermissionRoles.Delete);
Assert.IsFalse(options.Has(PermissionRepository.PermissionRoles.Delete)); // fails here since the value returned is Delete. So it basically removed all the others and left Delete. Am I confused or should the returned value be all the others from before less Delete?
Debug.WriteLine(options);
Rykie
April 6, 2010 at 12:51 am
I think the remove method should be an XOR function rather than an AND operation.
result = Convert.ToInt64(value) ^ (long)parsed.Signed;
Rykie
April 6, 2010 at 1:00 am
Ouch! Thank you for pointing it out – I’ll update it!
Changed from val1 & val2 to val1 & ~val2
hugoware
April 6, 2010 at 12:21 pm
Legend, works fantastic! Thanks, well done on this one!
Rykie
April 6, 2010 at 5:47 pm
This is great stuff. I had the need for a little bit more so I thought i’d share. Similar to the Has method but checks if ANY of the flags are set rather than ALL. (Apologies if this doesn’t format correctly)
///
/// Checks if an enumerated type contains any of the passed in values
///
public static bool HasAny(this Enum value, T check)
{
Type type = value.GetType();
//determine the values
object result = value;
_Value parsed = new _Value(check, type);
if (parsed.Signed is long)
{
return (Convert.ToInt64(value) & (long)parsed.Signed) != 0;
}
else if (parsed.Unsigned is ulong)
{
return (Convert.ToUInt64(value) & (ulong)parsed.Unsigned) != 0;
}
else
{
return false;
}
}
ilh
July 13, 2010 at 4:30 am
Cheers very helpful
Luke
October 7, 2010 at 9:06 pm
hugo – i just love it when the solution to a problem already exists. had been using the ‘one’ liner versions per enum for a long time until a requirement to be a little more generic popped up.
you saved the day – and many hours of trial and error. as a bonus, the code is clear and easy to follow whilst serving its purpose 1005.
thank you
jim
jim tollan
February 2, 2011 at 8:33 am