4

In some scenarios, when I pass a Enum to a method, I need to handle whether it is a single Enum value, or otherwise it is a flag combination, for that purpose I wrote this simple extension:

Vb.Net:

<Extension>
Public Function FlagCount(ByVal sender As System.[Enum]) As Integer
    Return sender.ToString().Split(","c).Count()
End Function

C# (online translation):

[Extension()]
public int FlagCount(this System.Enum sender) {
    return sender.ToString().Split(',').Count();
}

Example Usage:

Vb.Net:

Dim flags As FileAttributes = (FileAttributes.Archive Or FileAttributes.Compressed)
Dim count As Integer = flags.FlagCount()
MessageBox.Show(flagCount.ToString())

C# (online translation):

FileAttributes flags = (FileAttributes.Archive | FileAttributes.Compressed);
int count = flags.FlagCount();
MessageBox.Show(flagCount.ToString());

I just would like to ask If exists a more direct and efficient way that what I'm currently doing to avoid represent the flag combination as a String then split it.

Avi Turner
  • 10,234
  • 7
  • 48
  • 75
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • 1
    How about `Enum.GetValues(typeof(YourEnum)).OfType().Count(enumValue => yourValue.HasFlag(enumValue))`? – Corak May 01 '16 at 05:38
  • off-topic note: you can make your source code more readable for you and your team by omitting unnecessary keywords and qualifiers: You might want to change `Function FlagCount(ByVal sender As System.[Enum]) As Integer` to `Function FlagCount(sender As [Enum]) As Integer` (unlike in C#, in VB functions are public by default, `ByVal` is a relict, type qualifiers are superfluous, especially for `System`... unless you must code in Visual Studio 2010 or older) – miroxlav May 01 '16 at 06:31
  • @miroxlav I appreciate your suggestion but I think the opposite,the default implicits of Vb.Net does not means that those keywords should be ignored by the developer,`ByVal` keyword still exists, why?: to use it, Is not a bad practice,is less readable when the code contains params with `ByRef` and params without `ByVal`. On the other hand, I totally agree about the `System` namespace specification,but that has a good reason,in my real sourcecode the module that contains the extensions is named `Enum` for design reasons, then I need to specify the `System` namespace to avoid the disambiguation. – ElektroStudios May 01 '16 at 06:50
  • 2
    @ElektroStudios – no problem, thank you for answer. `ByVal` exists mainly for legacy reasons (you actually *never* need to name it) and IMHO '`ByRef` vs. nothing' is much more readable than '`ByRef` vs. `ByVal`' because `ByRef` is used only in few cases and nicely stands out when alone. But this was only side note... just keep what suits you the best. :) – miroxlav May 01 '16 at 07:10
  • miroxlav no matter, people have different likes/tastes (sorry for my bad English), thankyou too for your oppinion. And of course thanks to @Corak too. – ElektroStudios May 01 '16 at 07:27

2 Answers2

6

Option A:

public int FlagCount(System.Enum sender)
{
    bool hasFlagAttribute = sender.GetType().GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
    if (!hasFlagAttribute) // No flag attribute. This is a single value.
        return 1;

    var resultString = Convert.ToString(Convert.ToInt32(sender), 2);
    var count = resultString.Count(b=> b == '1');//each "1" represents an enum flag.
    return count;
}

Explanation:

  • If the enum does not have a "Flags attribute", then it is bound to be a single value.
  • If the enum has the "Flags attribute", Convert it to the bit representation and count the "1"s. each "1" represents an enum flag.

Option B:

  1. Get all flaged items.
  2. Count them...

The code:

public int FlagCount(this System.Enum sender)
{
  return sender.GetFlaggedValues().Count;
}

/// <summary>
/// All of the values of enumeration that are represented by specified value.
/// If it is not a flag, the value will be the only value returned
/// </summary>
/// <param name="value">The value.</param>
/// <returns></returns>
public static List<Enum> GetFlaggedValues(this Enum value)
{
    //checking if this string is a flagged Enum
    Type enumType = value.GetType();
    object[] attributes = enumType.GetCustomAttributes(true);

    bool hasFlags = enumType.GetCustomAttributes(true).Any(attr => attr is System.FlagsAttribute);
    //If it is a flag, add all flagged values
    List<Enum> values = new List<Enum>();
    if (hasFlags)
    {
        Array allValues = Enum.GetValues(enumType);
        foreach (Enum currValue in allValues)
        {
            if (value.HasFlag(currValue))
            {
                values.Add(currValue);
            }
        }
    }
    else//if not just add current value
    {
        values.Add(value);
    }
    return values;
}
Avi Turner
  • 10,234
  • 7
  • 48
  • 75
  • Thanks for answer, but I wonder if option A can produce an arithmetic overflow for some (U)Int64 enums (even using ToInt64 function), it could be?. – ElektroStudios May 01 '16 at 07:04
  • The option B is very more expensive than a string split since it uses LINQ, lists and arrays, please take into account that I'm looking for alternative(s) that improves my current code (which I think it can be refactored to be simpler with more efficiency), not to discover all kind of alternatives that exists (since that is good to know it too! and I appreciate it, but is not what I'm asking for), anyways I'll test it too to see if I have the expected result in all scenarios. – ElektroStudios May 01 '16 at 07:30
  • 1
    @ElektroStudios You are correct about the possible overflow, but it is rather easy to fix (e.g. check for underlying type and then convert etc...). Option B might be more expensive, i'm but I think it is worth to check if and how much it effect you specific application. For me, I found the 'GetFlaggedValues' very helpful... – Avi Turner May 01 '16 at 07:33
  • BTW you say that `option B is very more expensive than a string split` but remember, there are no free lunch. Someone has generated that string for you. I never checked how the `Enum.ToString()` is implemented, but it might be involving lists/arrays as well... – Avi Turner May 01 '16 at 07:36
1

Could not let this one go. Best practice when counting bits in an integer is not to convert to a string... Have we lost the ability to work with bits now we all use high level languages? ;)

Since the question was about the most efficient implementation, here is my answer. I have not tried hyper-optimised it as to do so would confuse it I think. I have also used the previous answer as a base to make comparison easier. There are two methods, one which counts the flags and one which exits early if you only want to know if it has a single flag. NB: You cannot remove the flag attribute check because standard non-flag enums can be any number also.

    public static int FlagCount(this System.Enum enumValue){
        var hasFlagAttribute = enumValue.GetType().GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
        if (!hasFlagAttribute)
            return 1;
        var count = 0;
        var value = Convert.ToInt32(enumValue);
        while (value != 0){
            if ((value & 1) == 1)
                count++;
            value >>= 1;
        }
        return count;
    }
    public static bool IsSingleFlagCount(this System.Enum enumValue){
        var hasFlagAttribute = enumValue.GetType().GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0;
        if (!hasFlagAttribute)
            return true;
        var isCounted = false;
        var value = Convert.ToInt32(enumValue);
        while (value != 0){
            if ((value & 1) == 1){
                if (isCounted)
                    return false;
                isCounted = true;
            }
            value >>= 1;
        }
        return true;
    }
Shane Paul
  • 21
  • 1
  • 3