4

Consider the following enumeration:

[Flags]
public enum EnumWithUniqueBitFlags
{
    None = 0,
    One = 1,
    Two = 2,
    Four = 4,
    Eight = 8,
}

[Flags]
public enum EnumWithoutUniqueFlags
{
    None = 0,
    One = 1,
    Two = 2,
    Four = 4,
    Five = 5,
}

The first one has values such that any combination will produce a unique bit combination while the second one will not. I need to programmatically determine this. So far I had only been checking if every value was a power of two but this since this code will be used to consume Enums developed by others, it is not practical.

var values = Enum.GetValues(typeof(TEnum)).OfType<TEnum>().ToList();

for (int i = 0; i < values.Count; i++)
{
    if (((int) ((object) values [i])) != ((int) Math.Pow(2, i)))
    {
        throw (new Exception("Whatever."));
    }
}

As opposed to the code above how could programatically determine that the following Enum meets the bit combination uniqueness objective (without assumptions such as values being powers of 2, etc.)?

[Flags]
public enum EnumWithoutUniqueFlags
{
    Two = 2,
    Four = 9,
    Five = 64,
}

Please ignore which integral type the enum derives from as well as the fact that values could be negative.

Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
  • 5
    A flags enum defining values which aren't powers of two is common - naming states that are superpositions of the states defined at the powers of two can be useful. – Preston Guillot Feb 02 '15 at 04:37

3 Answers3

4

You could compare the bitwise OR of enum values with the arithmetic sum of enum values:

var values = Enum.GetValues(typeof(EnumWithoutUniqueFlags))
    .OfType<EnumWithoutUniqueFlags>().Select(val => (int)val).ToArray();

bool areBitsUnique = values.Aggregate(0, (acc, val) => acc | val) == values.Sum();

EDIT
As the @usr mentioned, the code above works for positive values only.
Despite the fact that the OP requested to ignore:

Please ignore which integral type the enum derives from as well as the fact that values could be negative.

I'm glad to introduce more optimal single loop approach:

private static bool AreEnumBitsUnique<T>()
{
    int mask = 0;
    foreach (int val in Enum.GetValues(typeof(T)))
    {
        if ((mask & val) != 0)
            return false;
        mask |= val;
    }
    return true;
}

To make it work for other underlying type (uint, long, ulong), just change the type of the mask and val variables.

EDIT2
Since the Enum.GetValues method returns values in the ascending order of their unsigned magnitude, if you need to check for duplicates of zero value, you could use the following approach:

private static bool AreEnumBitsUnique<T>()
{
    int mask = 0;
    int index = 0;
    foreach (int val in Enum.GetValues(typeof(T)))
    {
        if ((mask & val) != 0)  // If `val` and `mask` have common bit(s)
            return false;
        if (val == 0 && index != 0) // If more than one zero value in the enum
            return false;
        mask |= val;
        index += 1;
    }
    return true;
}
Dmitry
  • 13,797
  • 6
  • 32
  • 48
  • Does not work for the following inputs: `new int[4]{ -98272, 65564, -98240, 129410 }`. Determined with Smart Unit Tests aka Microsoft Pex. It seems to work for all positive values. If negative values are allowed at least three are needed to find the bug. – usr Feb 02 '15 at 08:31
  • @Dmitry: Would it matter if one or more of the values in the enumeration have a value of zero? – Raheel Khan Feb 03 '15 at 07:44
  • @RaheelKhan Zero value(s) in an enum does not affect the method's return value. I've added a new method with check for duplicates of zero. Please revise. – Dmitry Feb 03 '15 at 11:12
2

To be unique, the enum must have no bits in common with other enum values. The bitwise AND operation can be used for this.

for (int i = 0; i < values.Count; ++i)
{
  for (int j = 0; j < values.Count; ++j)
  {
    if (i != j && ((values[i] & values[j]) != 0))
        throw new Exception(...);
  }
}
Richard Schneider
  • 34,944
  • 9
  • 57
  • 73
0

I would cast the enums to uints, do a bitwise not of a 0x0, use an xor and if the next value is less than the previous one you have a collision

public static bool CheckEnumClashing<TEnum>()
{
    uint prev = 0;
    uint curr = 0;


   prev = curr = ~curr;

    foreach(var target in Enum.GetValues(typeof(TEnum)).Select(a=>(uint)a))
    {
        curr ^=target;

        if( curr <= prev )
            return false;

        prev = curr; 
    }

    return true;
}
konkked
  • 3,161
  • 14
  • 19
  • What if enum values are for example `1`, `3`? Or any other values having another common bit, but not the highest bit. – Dmitry Feb 02 '15 at 05:20
  • @Dmitry you're right, thanks for the heads up, will try and salvage this – konkked Feb 02 '15 at 05:26