Search code examples
c#enumsbit-manipulationflags

Invert enum flags


Lets say I have the following flags:

[Flags]
public enum Foo
{
    None = 0,
    Foo1 = 1,
    Foo2 = 2,
    Foo4 = 4,
    Foo8 = 8
}

Now I have a variable foo:

var foo = Foo.Foo1 | Foo.Foo4;

What I want to get is the following the inverted flags of foo. That would mean something like this:

Foo.Foo2 | Foo.Foo8

I've tried the ~ operator. But since my enum is a int32 value it inverts all 32 bits. But I actually just would need to invert the bits used by my Foo enum.

EDIT: Foo1 | Foo4 would equal the following bitmask:

00000000 00000000 00000000 00000101

If you invert that bitmask by using the ~ operator you will get the following result:

11111111 11111111 11111111 11111010

The result I would like to have would be:

00000000 00000000 00000000 00001010

As you can see. I just would like to invert the bits USED by the Foo enumeration. Not all bits of the whole 32-integer value.


Solution

  • What you want to do is to combine all the values of the enum and then mask it with the complement of your current value.

            Foo value = Foo.Foo4;
            Foo allValues = (Foo)0;
            foreach (var v in Enum.GetValues(typeof(Foo)))
                allValues |= (Foo)v;
            var compliment = allValues & ~(value);
    

    Or, you could combine the values with Linq and cache them statically for performance:

        public static class FooHelper
        {
            private readonly static Foo allValues = ((Foo[])Enum.GetValues(typeof(Foo))).Aggregate((Foo)0, (all, cur) => all | cur);
    
            public static Foo AllValues { get { return allValues ; } }
        }
    

    And then later:

            var foo = Foo.Foo1 | Foo.Foo4;
            var compliment = FooHelper.AllValues & ~(foo);
    

    Update

    If you want a generic method to combine all the flag values of an enum, you can do this:

    var compliment = EnumHelper.GetAll<Foo>() & ~(value);
    

    where basic data about the enum is cached in a lazy parameterized singleton instance:

    /// <summary>
    /// Contains generic utilities for enums, constrained for enums only.
    /// </summary>
    public sealed class EnumHelper : Enums<Enum>
    {
        private EnumHelper()
        {
        }
    }
    
    /// <summary>
    /// For use by EnumHelper, not for direct use.
    /// </summary>
    public abstract class Enums<TEnumBase> where TEnumBase : class, IConvertible
    {
        // Generic singleton remembering basic properties about specified enums, cached for performance.
        sealed class DataSingleton<TEnum> where TEnum : struct, TEnumBase
        {
            static readonly DataSingleton<TEnum> instance = new DataSingleton<TEnum>();
    
            readonly bool isSigned;
            readonly TEnum allValues;
            readonly bool hasFlags;
    
            // Explicit static constructor to tell C# compiler
            // not to mark type as beforefieldinit
            static DataSingleton()
            {
            }
    
            DataSingleton()
            {
                isSigned = GetIsSigned();
                allValues = GetAll();
                hasFlags = GetHasFlags();
            }
    
            static bool GetHasFlags()
            {
                var attributes = typeof(TEnum).GetCustomAttributes(typeof(FlagsAttribute), false);
                return attributes != null && attributes.Length > 0;
            }
    
            static bool GetIsSigned()
            {
                var underlyingType = Enum.GetUnderlyingType(typeof(TEnum));
                bool isSigned = (underlyingType == typeof(long) || underlyingType == typeof(int) || underlyingType == typeof(short) || underlyingType == typeof(sbyte));
                bool isUnsigned = (underlyingType == typeof(ulong) || underlyingType == typeof(uint) || underlyingType == typeof(ushort) || underlyingType == typeof(byte));
                if (!isSigned && !isUnsigned)
                    throw new InvalidOperationException();
                return isSigned;
            }
    
            static TEnum GetAll()
            {
                if (GetIsSigned())
                {
                    long value = 0;
                    foreach (var v in Enum.GetValues(typeof(TEnum)))
                        // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                        value |= Convert.ToInt64(v, CultureInfo.InvariantCulture);
                    return (TEnum)Enum.ToObject(typeof(TEnum), value);
                }
                else
                {
                    ulong value = 0;
                    foreach (var v in Enum.GetValues(typeof(TEnum)))
                        // Not sure I need the culture but Microsoft passes it in Enum.ToUInt64(Object value) - http://referencesource.microsoft.com/#mscorlib/system/enum.cs
                        value |= Convert.ToUInt64(v, CultureInfo.InvariantCulture);
                    return (TEnum)Enum.ToObject(typeof(TEnum), value);
                }
            }
    
            public bool HasFlags { get { return hasFlags; } }
    
            public bool IsSigned { get { return isSigned; } }
    
            public TEnum AllValues { get { return allValues; } }
    
            public static DataSingleton<TEnum> Instance { get { return instance; } }
        }
    
        private static void ThrowOnEnumWithoutFlags<TEnum>(DataSingleton<TEnum> data) where TEnum : struct, TEnumBase
        {
            if (!data.HasFlags)
            {
                throw (new ArgumentException("The generic argument [<TEnum>] must be an enumeration with the [FlagsAttribute] applied.", "TEnum: " + typeof(TEnum).FullName));
            }
        }
    
        public static TEnum GetAll<TEnum>() where TEnum : struct, TEnumBase
        {
            var data = DataSingleton<TEnum>.Instance;
            ThrowOnEnumWithoutFlags<TEnum>(data);
            return data.AllValues;
        }
    }