10

I have a flags enum defined like this:

[Flags]
public enum MyEnum
{
    None =     0x00,
    Choice1 =  0x01,
    Choice2 =  0x02,
    Choice3 =  0x04,
    Default =  Choice1 | Choice2,
    All =      Default | Choice3
}

I would like a way to print out which flags are included in MyEnum.Default. In this case, I'd want the output to be something like "Choice1, Choice2".

The problem with simply printing MyEnum.Default.ToString() is that the output would be "Default" when I want "Choice1, Choice2".

Here's one option, but if I used this I'd have to update the printing every time I changed the enum.

((StudyData.Choice1 & StudyData.Default) == StudyData.Choice1 ? StudyData.Choice1.ToString() : "") + ", " +
((StudyData.Choice2 & StudyData.Default) == StudyData.Choice2 ? StudyData.Choice2.ToString() : "") + ", " +
((StudyData.Choice3 & StudyData.Default) == StudyData.Choice3 ? StudyData.Choice3.ToString() : "")

Does anyone have a cleaner way of doing this? Ideally, I'd like a way of printing out the flags included in MyEnum.Default without having to change the printing code every time I added a new flag or changed the default.

Thanks!

Brian
  • 1,201
  • 2
  • 14
  • 26

6 Answers6

17

Using the extension methods I've written here on a related question, this should be simple:

var value = MyEnum.Default;
var str = String.Join(", ", value.GetIndividualFlags());
// "Choice1, Choice2"

And here's the extension methods:

static class EnumExtensions
{
    public static IEnumerable<Enum> GetFlags(this Enum value)
    {
        return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
    }

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
    {
        return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
    }

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
    {
        ulong bits = Convert.ToUInt64(value);
        List<Enum> results = new List<Enum>();
        for (int i = values.Length - 1; i >= 0; i--)
        {
            ulong mask = Convert.ToUInt64(values[i]);
            if (i == 0 && mask == 0L)
                break;
            if ((bits & mask) == mask)
            {
                results.Add(values[i]);
                bits -= mask;
            }
        }
        if (bits != 0L)
            return Enumerable.Empty<Enum>();
        if (Convert.ToUInt64(value) != 0L)
            return results.Reverse<Enum>();
        if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
            return values.Take(1);
        return Enumerable.Empty<Enum>();
    }

    private static IEnumerable<Enum> GetFlagValues(Type enumType)
    {
        ulong flag = 0x1;
        foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
        {
            ulong bits = Convert.ToUInt64(value);
            if (bits == 0L)
                //yield return value;
                continue; // skip the zero value
            while (flag < bits) flag <<= 1;
            if (flag == bits)
                yield return value;
        }
    }
}
Community
  • 1
  • 1
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
  • 2
    Note that if your enum has negative values (for example with Visual Studio extensibility framework, Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS) then the use of ulong and ToUInt64 here will cause problems. If you replace with long and ToInt64 then it'll handle enums with negative values. – Nerdtron Nov 06 '17 at 15:34
11

Decorate your enum with FlagsAttribute. It does pretty much exactly what you're after:

[Flags]
public enum FooNum
{
    foo = 0,
    bar = 1,
    lulz = 2,
    borkbork = 4
}

FooNum f = FooNum.bar | FooNum.borkbork;

Debug.WriteLine(f.ToString());

should give you:

bar, borkbork

martijnn2008
  • 3,552
  • 5
  • 30
  • 40
Pete M
  • 2,008
  • 11
  • 17
  • Oops! I forgot to include the FlagsAttribute in my copy-paste. I'm going to fix the original question. Thanks for the fast response and sorry for the confusion... – Brian Apr 04 '11 at 18:48
  • 2
    Ohhhh... I see what you're doing now. The problem with this one is even if you manually construct the `Default` with `yourEnum = yourEnum.Choice1 | yourEnum.Choice2` you will STILL get `yourEnum.Default` when you push it out to a string. How badly do you need the Default and All definitions on the enum itself? You're going to have to walk the bits and pull them separately like you've already found out. It's going to be a little messy no matter what. A different option would be to store your defaults as their own FooNum. That's messy in its own way though... – Pete M Apr 04 '11 at 19:04
  • 1
    The gotcha that might be getting some people looking at this is that you need to set a value for each of your enum values that are unique and is a power of 2, like 1, 2, 4, 8, etc. If you don't set a value, the implicit values will be 0, 1, 2, 3, etc. So when you pipe them together (binary OR), it will not give the expected value. – 4thex Nov 01 '19 at 15:16
2

Print by single linq statement:

var names = Enum.GetValues(typeof(MyEnum))
    .Cast<MyEnum>()
    .Where(a => (values & a) == a)
    .Select(a => a.ToString())
    .Aggregate((current, next) => current + ", " + next);

Updated version to print only explicitly defined values:

var values = MyEnum.All;

var allAttrs = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>();

var names = allAttrs
    // leave only explicitly defined and not zero values
    .Where(attr => allAttrs.Count(a => a != 0 && (attr & a) == a) == 1)   
    .Where(a => (values & a) == a)
    .Select(a=>a.ToString())
    .Aggregate((current, next) => current + ", " + next);

Console.WriteLine(names); // Choice1, Choice2, Choice3
SergeyA
  • 4,427
  • 1
  • 22
  • 15
1
using System;
using System.Collections.Generic;

using System.Text;

namespace printStar
{
    class Program
    {
        static void Main(string[] args)
        {


            Console.WriteLine("Enter the value ");
            int k = int.Parse(Console.ReadLine());
            int n = k - 1;
            int x = 2 * (k - 1) + 1;

            for (int p = 0; p <= n; p++)
            {
                for (int j = k - 1; j >= 0; j--)
                {
                    Console.Write(" ");
                }

                for (int i = 0; i <= (x - 2 * (k - 1)); i++)
                {
                    if (i % 2 == 1)
                    {
                        Console.Write("*");
                    }
                    else
                    {
                        Console.Write(" ");
                    }
                }

                Console.WriteLine();
                k--;
            }
            Console.ReadLine();
        }
    }
}
Jason Sturges
  • 15,855
  • 14
  • 59
  • 80
SANJU
  • 11
  • 1
1

I solved this in the shortest, clearest code possible that I expect performs well, although there is boxing in a couple of places. Using your type as an example:

MyEnum e = MyEnum.Choice1 | MyEnum.Choice2;
string s = FlagsEnumToString<MyEnum>(e); // Returns "Choice1, Choice2"

This is how it's implemented:

const string Separator = ", ";

public static string FlagsEnumToString<T>(Enum e)
{
    var str = new StringBuilder();

    foreach (object i in Enum.GetValues(typeof(T)))
    {
        if (IsExactlyOneBitSet((int) i) &&
            e.HasFlag((Enum) i))
        {
            str.Append((T) i + Separator);
        }
    }

    if (str.Length > 0)
    {
        str.Length -= Separator.Length;
    }

    return str.ToString();
}

static bool IsExactlyOneBitSet(int i)
{
    return i != 0 && (i & (i - 1)) == 0;
}

Some comments might come up and I'll address these first:

I need to call your method providing both type and variable?

Because this can't be done with a generic T argument implicitly. T can't be cast to Enum for use with HasFlag. No, also not using where T : struct, IConvertible.

The foreach also uses object?

Yes, also to be able to cast. Only object can be cast to the other types T, int, Enum.

I think this can be optimized by casting to int inside the loop once with a temporary variable.

I think so, yes. This code was written like this for clarity. So yes do that and get rid of those HasFlag calls if you like.

I think you still can use Enum as the foreach variable and save on casting.

No, because you need a cast to T and that can only be done from object. There might be 'better' solutions but this is most certainly the shortest and clearest one.

Kay Zed
  • 1,304
  • 2
  • 21
  • 31
1

Use flags.ToString("g");
See Enumeration Format Strings

user626528
  • 13,999
  • 30
  • 78
  • 146