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


131

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


API를 제어 할 수있는 경우 비트 플래그를 사용하지 마십시오. 유용한 최적화는 거의 없습니다. 여러 공개 'bool'필드와 함께 구조체를 사용하는 것은 의미 상 동일하지만 코드가 훨씬 간단합니다. 나중에 필요한 경우 필드를 비트 필드를 내부적으로 조작하는 속성으로 변경하여 최적화를 캡슐화 할 수 있습니다.
Jay Bazuzi

2
나는 당신이 말하는 것을 보았고 많은 경우에 의미가있을 것입니다. 그러나이 경우에는 If 제안과 같은 문제가 있습니다 ... 대신 12 개의 다른 bool에 대해 If 문을 작성해야합니다 배열에 간단한 foreach 루프를 사용합니다. (이것은 공개 DLL의 일부이기 때문에, 단지

10
"코드가 획기적으로 단순 해집니다."개별 비트를 테스트하는 것 이외의 다른 작업을 수행하는 경우에는 완전히 반대입니다. 루프와 세트 작업은 필드 및 속성이 첫 번째 클래스 엔터티가 아니기 때문에 사실상 불가능합니다.
Jim Balter

2
@nawfal "조금"나는 당신이 무엇을했는지
봅니다

답변:


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

7
HasFlag이후 .NET 4에서 사용할 수 있습니다.
Andreas Grech 2014 년

3
대단해! 그러나 더 간단하고 사용하기 쉽게 만들 수 있습니다. 이것을 확장 방법으로 고수하십시오. Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); 그러면 그냥 : myEnum.GetFLags():)
joshcomley

3
훌륭한 1 라이너, 조쉬, 그러나 위의 Jeff의 대답에서와 같이 여전히 단일 플래그 값 (Bar, Baz) 대신 멀티 플래그 값 (Boo)을 선택하는 문제가 여전히 있습니다.

10
Nice-None 's 조심하지만-예를 들어 Items. Jeff의 답은 항상 포함되지 않습니다
Ilan

1
메소드 서명은static IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers

48

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

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

7
왜 맨 위에 있지 않습니까? :) .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));표시 할 값이 0 인 경우 사용None
georgiosd

슈퍼 청소. 내 의견으로는 최고의 솔루션입니다.
Ryan Fiorini

@georgiosd 나는 성능이 그렇게 좋지 않다고 생각합니다. (그러나 대부분의 작업에는 충분해야 함)
AntiHeadshot

41

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

[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

문자열 분할을 고려했지만 전체 열거 형의 비트 값을 반복하는 것보다 훨씬 많은 오버 헤드 일 것입니다.

불행히도 이렇게하면 중복 값을 테스트해야합니다 (원치 않는 경우). 내 두 번째 예를 참조하십시오, 그것은 얻을 것이다 Bar, Baz그리고 Boo대신의 Boo.
Jeff Mercado

내가하고있는 일에 대해 그 부분이 불필요하지만 (실제로 나쁜 생각 :)) Boo를 꺼낼 수 있다는 것에 흥미가 있습니다.

이것은 흥미로워 보이지만, 오해가 없다면 위의 열거에 대해 Boo를 반환합니다. 여기서 다른 값의 조합이 아닌 버전 (즉, 2의 거듭 제곱)을 열거하고 싶습니다. . 그 일을 쉽게 할 수 있었습니까? 나는 FP 수학에 의지하지 않고 그것을 식별하는 쉬운 방법을 생각하지 못했습니다.

@Robin : 맞습니다. 원본은 Boo(를 사용하여 반환 된 값)을 반환 ToString()합니다. 개별 플래그 만 허용하도록 조정했습니다. 내 예에서 BarBaz대신에 얻을 수 있습니다 Boo.
Jeff Mercado

26

몇 년 후 조금 더 많은 경험을 바탕으로 다시 돌아와서, 단일 비트 값에 대한 나의 궁극적 인 대답은 가장 낮은 비트에서 가장 높은 비트로 이동하는 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의 속도를 향상 시켰을 가능성이 있습니다. 아무것도 알지 못했지만 테스트하지 않았습니다.)


그냥 노트 플래그는 같은 비트로 ULONG로 초기화되어야한다에 있어야한다 int로 초기화 1ul
forcewill

고마워, 내가 고칠거야! (방금 가서 실제 코드를 확인했으며 구체적으로 ulong이라고 선언하여 다른 방법으로 수정했습니다.)

2
이것은 내가 찾은 유일한 해결책입니다. "None"을 나타내는 값이 0 인 플래그가 있으면 다른 응답 GetFlag () 메서드가 YourEnum.None을 플래그 중 하나로 반환합니다. 실제로 열거 형이 아니더라도 메소드를 실행하고 있습니다! 0이 아닌 열거 형 플래그가 하나만 설정된 경우 메소드가 예상보다 많은 시간 동안 실행되어 이상한 복제 로그 항목이 표시되었습니다. 이 훌륭한 솔루션을 업데이트하고 추가하는 데 시간을 내 주셔서 감사합니다!
BrianH

yield return bits;?
Jaider

1
ulong.MaxValue를 할당 한 "All"열거 형 변수를 원했기 때문에 모든 비트가 '1'로 설정되었습니다. 그러나 플래그 <비트가 true로 평가되지 않기 때문에 코드가 무한 루프에 도달합니다 (플래그가 음수로 루프 된 다음 0에 멈춤).
Haighstrom

15

@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.


4
아마도 flags매개 변수가 제네릭 형식 이기를 원했을 수도 T있습니다. 그렇지 않으면 매번 열거 형을 명시 적으로 지정해야합니다.
Ray

11

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

3

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

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

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

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

5
.Net 4.0을 사용하는 경우 동일한 방법을 수행하는 데 사용할 수있는 확장 방법 HasFlag가 있습니다. myVar.HasFlag (FlagsEnum.Flag1)
pstrjds

1
프로그래머가 비트 단위 AND 연산을 이해할 수없는 경우이를 포장하고 새로운 경력을 찾아야합니다.
Ed S.

2
@Ed : 사실이지만 HasFlag는 코드를 다시 읽을 때 더 좋습니다. (몇 달 또는 몇 년 후에)
Dr TJ

4
@Ed Swangren : 비트 연산을 사용하는 것이 "하드"이기 때문에 코드를 더 읽기 쉽고 덜 간결하게 만드는 것이 중요합니다.
Jeff Mercado

2
HasFlag는 엄청 느립니다. HasFlag 대 비트 마스킹을 사용하여 큰 루프를 시도하면 큰 차이가 있음을 알 수 있습니다.

3

내가 한 일은 대신 같은 방법의 입력 매개 변수를 입력하는 나의 접근 방식을 변경 하였다 enum유형을, 나는의 배열로 입력 enum유형 ( MyEnum[] myEnums이런 식으로 그냥으로 반복 루프 내부의 스위치 문을 사용하여 배열을 통해).


2

비록 그들이 시작되었지만 위의 답변에 만족하지 못했습니다.

여기에 몇 가지 다른 소스를 조립할 후 :
이전 포스터이 thread의 SO 문의 글에
코드 프로젝트 열거 플래그 포스트를 확인
위대한 열거 <T> 유틸리티

나는 이것을 만들었으므로 당신의 생각을 알려주십시오.
매개 변수 :
bool checkZero: 0플래그 값 으로 허용하도록 지시합니다 . 기본적 input = 0으로 비어 있습니다.
bool checkFlags: 속성으로 Enum장식되어 있는지 확인하도록 지시 [Flags]합니다.
추신. checkCombinators = false비트를 조합 한 열거 형 값을 무시하도록 alg 을 알아낼 시간이 없습니다 .

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true)
    {
        Type enumType = typeof(TEnum);
        if (!enumType.IsEnum)
            yield break;

        ulong setBits = Convert.ToUInt64(input);
        // if no flags are set, return empty
        if (!checkZero && (0 == setBits))
            yield break;

        // if it's not a flag enum, return empty
        if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false))
            yield break;

        if (checkCombinators)
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum<TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }
        else
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum <TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }

    }

이것은 내가 찾은 헬퍼 클래스 Enum <T> 을 사용 yield return합니다 GetValues.

public static class Enum<TEnum>
{
    public static TEnum Parse(string value)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public static IEnumerable<TEnum> GetValues()   
    {
        foreach (object value in Enum.GetValues(typeof(TEnum)))
            yield return ((TEnum)value);
    }
}  

마지막으로이를 사용하는 예는 다음과 같습니다.

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

죄송합니다,이 프로젝트를 며칠 안에 볼 시간이 없었습니다. 코드를 잘 살펴보면 다시 연락 드리겠습니다.

4
해당 코드에 버그가 있음을 알 수 있습니다. if (checkCombinators) 문의 두 분기 코드는 동일합니다. 또한 버그가 아니지만 예기치 않은 것은 0에 대해 열거 된 열거 형 값이 있으면 항상 컬렉션에 반환된다는 것입니다. checkZero가 true이고 다른 플래그가 설정되지 않은 경우에만 반환되어야합니다.
dhochee

@dhochee. 나는 동의한다. 또는 코드가 꽤 좋지만 인수가 혼란 스럽습니다.
AFract

2

위의 Greg의 대답을 바탕으로 None = 0과 같이 열거 형에 0 값이있는 경우를 처리합니다.이 경우 해당 값을 반복해서는 안됩니다.

public static IEnumerable<Enum> ToEnumerable(this Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value) && Convert.ToInt64(value) != 0)
            yield return value;
}

누구나 열거 형의 모든 플래그가 모든 기본 열거 형 유형과 All = ~ 0 및 All = EnumValue1 | EnumValue2 | EnumValue3 | ...


1

Enum에서 Iterator를 사용할 수 있습니다. MSDN 코드에서 시작 :

public class DaysOfTheWeek : System.Collections.IEnumerable
{
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 };
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    public string value { get; set; }

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            if value >> i & 1 == dayflag[i] {
                yield return days[i];
            }
        }
    }
}

테스트를 거치지 않았으므로 실수를 한 경우 언제든지 전화하십시오. (분명히 재진입하지는 않습니다.) 미리 값을 할당하거나 enum.dayflag 및 enum.days를 사용하는 다른 함수로 분류해야합니다. 당신은 개요와 어딘가에 갈 수 있습니다.


0

다음 코드와 같을 수 있습니다.

public static string GetEnumString(MyEnum inEnumValue)
{
    StringBuilder sb = new StringBuilder();

    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum )))
    {
        if ((e & inEnumValue) != 0)
        {
           sb.Append(e.ToString());
           sb.Append(", ");
        }
    }

   return sb.ToString().Trim().TrimEnd(',');
}

열거 형 값이 값에 포함 된 경우에만 내부로 들어갑니다.


0

모든 답변은 간단한 플래그로 잘 작동하므로 플래그가 결합되면 문제가 발생할 수 있습니다.

[Flags]
enum Food
{
  None=0
  Bread=1,
  Pasta=2,
  Apples=4,
  Banana=8,
  WithGluten=Bread|Pasta,
  Fruits = Apples | Banana,
}

열거 형 값 자체가 조합인지 테스트하기 위해 검사를 추가해야 할 수도 있습니다. 귀하 의 요구 사항을 충족 시키기 위해 Henk van Boeijen 이 게시 한 것과 같은 것이 필요할 것입니다 (아래로 스크롤해야 함)


0

새로운 Enum 제약 조건과 제네릭을 사용하여 캐스팅을 방지하는 확장 방법 :

public static class EnumExtensions
{
    public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
    {
        return Enum
            .GetValues(typeof(T))
            .Cast<T>()
            .Where(e => flagsEnumValue.HasFlag(e))
            .ToArray();
    }
}

-1

int로 변환하여 직접 수행 할 수 있지만 유형 검사가 느슨해집니다. 가장 좋은 방법은 내 제안과 비슷한 것을 사용하는 것입니다. 올바른 유형을 유지합니다. 변환이 필요하지 않습니다. 복싱으로 인해 성능이 약간 저하되는 것은 완벽하지 않습니다.

완벽하지는 않지만 (권투) 경고없이 작동합니다 ...

/// <summary>
/// Return an enumerators of input flag(s)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static IEnumerable<T> GetFlags<T>(this T input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
    {
        if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0.
        {
            if (((Enum) (object) input).HasFlag(value))
                yield return (T) (object) value;
        }
    }
}

용법:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.