Hugoware

The product of a web developer with a little too much caffeine

Enumeration Extensions 2.0

with 8 comments

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!

Written by hugoware

February 23, 2010 at 10:26 pm

8 Responses

Subscribe to comments with RSS.

  1. This type of thing makes code so much easier to read and maintain. Thanks!

    Visual C# Kicks

    March 19, 2010 at 2:16 pm

  2. 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

  3. 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

  4. Legend, works fantastic! Thanks, well done on this one!

    Rykie

    April 6, 2010 at 5:47 pm

  5. 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

  6. Cheers very helpful

    Luke

    October 7, 2010 at 9:06 pm

  7. 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


Leave a comment