简体   繁体   English

打印标志枚举作为单独的标志

[英]Printing Flags Enum as Separate Flags

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 . 我想要一种方法来打印MyEnum.Default中包含哪些标志。 In this case, I'd want the output to be something like "Choice1, Choice2". 在这种情况下,我希望输出类似于“Choice1,Choice2”。

The problem with simply printing MyEnum.Default.ToString() is that the output would be "Default" when I want "Choice1, Choice2". 简单地打印MyEnum.Default.ToString()是当我想要“Choice1,Choice2”时输出将是“Default”。

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. 理想情况下,我想要一种打印MyEnum.Default包含的标志的方法,而不必在每次添加新标志或更改默认值时更改打印代码。

Thanks! 谢谢!

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;
        }
    }
}

Decorate your enum with [FlagsAttribute] . [FlagsAttribute]装饰你的枚举。 It does pretty much exactly what you're after: 它几乎完全符合您的要求:

[FlagsAttribute]
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 酒吧,borkbork

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();
        }
    }
}

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参数。 T can't be cast to Enum for use with HasFlag . T不能转换为Enum以与HasFlag一起使用。 No, also not using where T : struct, IConvertible . 不,也不使用where T : struct, IConvertible

The foreach also uses object ? foreach还使用object

Yes, also to be able to cast. 是的,也是为了能够施展。 Only object can be cast to the other types T , int , Enum . 只有object可以转换为其他类型TintEnum

I think this can be optimized by casting to int inside the loop once with a temporary variable. 我认为这可以通过使用临时变量在循环内部转换为int来优化。

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. 所以是的,如果你愿意,可以摆脱那些HasFlag调用。

I think you still can use Enum as the foreach variable and save on casting. 我认为你仍然可以使用Enum作为foreach变量并节省转换。

No, because you need a cast to T and that can only be done from object . 不,因为你需要一个强制转换为T而且只能从object完成。 There might be 'better' solutions but this is most certainly the shortest and clearest one. 可能有“更好”的解决方案,但这绝对是最短且最清晰的解决方案。

Print by single linq statement: 单个linq声明打印:

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

Use flags.ToString("g"); 使用flags.ToString("g");
See Enumeration Format Strings 请参阅枚举格式字符串

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM