Welcome to CSharp Labs

Validating Enum Values and Flags with Extensions

Thursday, June 27, 2013

Enumeration constants and bit flags may require validation if read from a stream or user input. I have created enumeration validation extension methods that check constants and flags for System.Enum types with unique constants or valid flags.

How it Works

Enumerations with unique values can be validated through the framework's Enum.IsDefined method. However, if a value is a combination of bit flags, the IsDefined method will return false even if the flags are valid.

To validate flags, I check if the value is valid or perform a bitwise NOT, for each enumeration constant. Take a look at the AreFlagsDefined method used internally to validate enumerations that can be treated as a set of flags:

        /// <summary>
        /// Determines if the enumeration value contains a combination of valid flags.
        /// </summary>
        /// <param name="value">A bit combination of constant values.</param>
        /// <param name="enumType">An enumeration type.</param>
        /// <returns>true if all flags are defined in the enumeration; otherwise, false.</returns>
        private static bool AreFlagsDefined(this Enum value, Type enumType)
        {
            //get the enumeration type code describing underlying type:
            TypeCode code = value.GetTypeCode();

            switch (code)
            {
                case TypeCode.Boolean:
                case TypeCode.Char:
                case TypeCode.Byte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64: //validate unsigned value
                    {
                        ulong flags = Convert.ToUInt64(value); //convert to 64-bit unsigned integer
                        ulong flag;

                        //iterate enumeration constants:
                        foreach (object obj in Enum.GetValues(enumType))
                        {
                            flag = Convert.ToUInt64(obj); //get flag constants

                            if (flag == flags) //last flag
                                return true;

                            //remove bits:
                            flags &= ~flag;
                        }
                    }
                    break;
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64: //validate signed value        
                    {
                        long flags = Convert.ToInt64(value); //convert to 64-bit signed integer
                        long flag;

                        //iterate enumeration constants:
                        foreach (object obj in Enum.GetValues(enumType))
                        {
                            flag = Convert.ToInt64(obj); //get flag constants

                            if (flag == flags) //last flag
                                return true;

                            //remove bits:
                            flags &= ~flag;
                        }
                    }
                    break;
                default:
                    throw new ArgumentException(String.Format("Cannot validate System.Enum '{0}' with TypeCode: '{1}'.", enumType, code), "value");
            }

            return false;
        }

Implementation requires two separate algorithms to handle signed and unsigned underlying types. This is due to the IConvertable implementation throwing OverflowExceptions.

Caveats
  • If the enumeration contains constants that are not mutually exclusive, flag validation may yield incorrect results.
  • If the enumeration is treated as a set of flags without the FlagsAttribute specified, validation will call the Enum.IsDefined method, potentially returning undesired results.
Using

I whipped up a unit test class to demonstrate enumeration validation successes and failures:

    [TestClass]
    public class EnumValidationExtensionsTests
    {
        /// <summary>
        /// Defines an enumeration of flags.
        /// </summary>
        [Flags]
        private enum EnumFlags : int
        {
            A = 0x1,
            B = 0x2,
            C = 0x4,
            D = 0x8,
            E = 0x10,
            F = 0x20
        }

        /// <summary>
        /// Demonstrates enumeration flag validation.
        /// </summary>
        [TestMethod]
        public void TestEnumFlagValidation()
        {
            Assert.IsTrue((EnumFlags.A | EnumFlags.B | EnumFlags.C).IsDefined());
            Assert.IsTrue((EnumFlags.A | EnumFlags.C | EnumFlags.F).IsDefined());
            Assert.IsFalse((EnumFlags.A | (EnumFlags)0xff).IsDefined());
            Assert.IsTrue((EnumFlags.A | (EnumFlags)0x2).IsDefined());
            Assert.IsTrue(EnumFlags.E.IsDefined());
            Assert.IsTrue(((EnumFlags)0x2).IsDefined());
            Assert.IsTrue(((EnumFlags)0x2 | (EnumFlags)0x10).IsDefined());
            Assert.IsFalse(((EnumFlags)0x40).IsDefined());
            Assert.IsFalse(((EnumFlags)0).IsDefined());
        }

        /// <summary>
        /// Defines an enumeration of constants.
        /// </summary>
        private enum EnumConstants : int
        {
            A = 0x1,
            B = 0x2,
            C = 0x3,
            D = 0x4,
            E = 0x5,
            F = 0x6
        }

        /// <summary>
        /// Demonstrates constant enumeration values validation.
        /// </summary>
        [TestMethod]
        public void TestEnumConstantValidation()
        {
            Assert.IsFalse((EnumConstants.A | EnumConstants.C | EnumConstants.F).IsDefined());
            Assert.IsTrue(EnumConstants.A.IsDefined());
            Assert.IsTrue(((EnumConstants)6).IsDefined());
            Assert.IsFalse(((EnumConstants)0x1 | (EnumConstants)0x6).IsDefined());
        }

        /// <summary>
        /// Defines a enumeration with the FlagsAttribute missing.
        /// </summary>
        private enum EnumFlagsWithoutFlagsAttribute : int
        {
            A = 0x1,
            B = 0x2,
            C = 0x4,
            D = 0x8,
            E = 0x10,
            F = 0x20
        }

        /// <summary>
        /// Demonstrates how a combination of flags on an enumeration with congruent values may inadvertently return defined.
        /// </summary>
        [TestMethod]
        public void TestEnumValidationAnomaly_ConstantsAsFlags()
        {
            //fails due to bitwise OR (0x1 | 0x2) = 0x3 being defined:
            Assert.IsFalse((EnumConstants.A | EnumConstants.B).IsDefined());
        }

        /// <summary>
        /// Demonstrates how a missing FlagsAttribute causes the IsDefined method to call the Enum.IsDefined method
        /// which does not detect flags.
        /// </summary>
        [TestMethod]
        public void TestEnumValidationAnomaly_FlagsWithoutFlagsAttribute()
        {
            //fails due to IsDefined method not finding FlagsAttribute and using Enum.IsDefined:
            Assert.IsTrue((EnumFlagsWithoutFlagsAttribute.A | EnumFlagsWithoutFlagsAttribute.B).IsDefined());
        }
    }

Pay special attention to how tests may fail while treating enumerations as flags with constants without mutually exclusive values and a missing FlagsAttribute causes EnumValidationExtensions.IsDefined to return false on seemingly valid flags.

An example of an enumeration that should not be validated is the System.Windows.Forms.Keys enumeration. Microsoft cautions not to use the enumeration for bitwise operations even though the FlagsAttribute is specified. Calling the EnumValidationExtensions.IsDefined method on the enumeration may yield incorrect results.

Source Code

Download EnumValidationExtensions

Comments