[C#] 플래그가있는 Enum의 값을 반복하는 방법은 무엇입니까?

플래그 열거 형을 보유하는 변수가있는 경우 어떻게 든 특정 변수의 비트 값을 반복 할 수 있습니까? 아니면 Enum.GetValues를 사용하여 전체 열거 형을 반복하고 어느 것이 설정되어 있는지 확인해야합니까?



답변

static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}


답변

다음은 문제에 대한 Linq 솔루션입니다.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}


답변

내가 아는 한 각 구성 요소를 가져 오는 기본 제공 방법은 없습니다. 그러나 다음과 같은 방법으로 얻을 수 있습니다.

[Flags]
enum Items
{
    None = 0x0,
    Foo  = 0x1,
    Bar  = 0x2,
    Baz  = 0x4,
    Boo  = 0x6,
}

var value = Items.Foo | Items.Bar;
var values = value.ToString()
                  .Split(new[] { ", " }, StringSplitOptions.None)
                  .Select(v => (Items)Enum.Parse(typeof(Items), v));

// This method will always end up with the most applicable values
value = Items.Bar | Items.Baz;
values = value.ToString()
              .Split(new[] { ", " }, StringSplitOptions.None)
              .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo

Enum내부적으로 문자열을 생성하여 대신 플래그를 반환 하도록 조정했습니다 . 리플렉터에서 코드를 볼 수 있으며 다소 동등해야합니다. 여러 비트를 포함하는 값이있는 일반적인 사용 사례에 적합합니다.

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

확장 방법 GetIndividualFlags() 는 유형에 대한 모든 개별 플래그를 가져옵니다. 따라서 여러 비트를 포함하는 값은 제외됩니다.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz


답변

몇 년 후 조금 더 많은 경험을 바탕으로 다시 돌아와서, 단일 비트 값에 대한 나의 궁극적 인 대답은 가장 낮은 비트에서 가장 높은 비트로 이동하는 Jeff Mercado의 내부 루틴의 약간의 변형입니다.

public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value))
        {
            yield return value;
        }
    }
}

그것은 효과가있는 것으로 보이며 몇 년 전의 이의 제기에도 불구하고 HasFlag를 사용합니다. 비트 비교를 사용하는 것보다 훨씬 읽기 쉽고 속도 차이는 내가 할 일에 중요하지 않기 때문입니다. (그들은 어쨌든 HasFlags의 속도를 향상 시켰을 가능성이 있습니다. 아무것도 알지 못했지만 테스트하지 않았습니다.)


답변

@Greg의 메소드에서 벗어나고 C # 7.3의 새로운 기능을 추가하면 다음과 같은 Enum제약이 있습니다.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
    where T : Enum    // New constraint for C# 7.3
{
    foreach (Enum value in Enum.GetValues(flags.GetType()))
        if (flags.HasFlag(value))
            yield return (T)value;
}

새로운 제약 조건을 사용하면을 통해 캐스트 할 필요없이 확장 방법이 될 수 있으며 메소드 (int)(object)e를 사용하여 HasFlag직접 캐스트 할 수 T있습니다.value .

C # 7.3은 지연 및에 대한 제약 조건도 추가했습니다 unmanaged.


답변

@ RobinHood70에서 제공 한 답변에 +1 나는 그 방법의 일반적인 버전이 나에게 편리하다는 것을 알았다.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("The generic type parameter must be an Enum.");

    if (flags.GetType() != typeof(T))
        throw new ArgumentException("The generic type parameter does not match the target type.");

    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}


C # 7.3을 솔루션 공간으로 가져 오기위한 @AustinWBryan의 편집 및 +1

public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}


답변

모든 값을 반복 할 필요는 없습니다. 특정 플래그를 다음과 같이 확인하십시오.

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
   //do something...
}

또는 ( pstrjds 가 주석에서 말했듯이) 다음과 같이 사용 되는지 확인할 수 있습니다.

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}