development

Flags Enum의 값을 반복합니까?

big-blog 2020. 7. 14. 07:43
반응형

Flags Enum의 값을 반복합니까?


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


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

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

[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

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

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

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


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

@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를 사용하여 from에서 HasFlag직접 캐스트 할 수 T있습니다 value.

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


위의 답변은 시작이지만 만족하지 않았습니다.

여기 함께 몇 가지 다른 소스를 조립할 후 :
이전 포스터이 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;
    }

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

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

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

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

What I did was change my approach, instead of typing the input parameter of the method as the enum type, I typed it as an array of the enum type (MyEnum[] myEnums), this way I just iterate through the array with a switch statement inside the loop.


Building upon Greg's answer above, this also takes care of the case where you have a value 0 in your enum, such as None = 0. In which case, it should not iterate over that value.

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

Would anyone know how to improve upon this even further so that it can handle the case where all flags in the enum are set in a super smart way that could handle all underlying enum type and the case of All = ~0 and All = EnumValue1 | EnumValue2 | EnumValue3 | ...


You can use an Iterator from the Enum. Starting from the MSDN code:

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

It's not tested, so if I made a mistake feel free to call me out. (obviously it's not re-entrant.) You'd have to assign value beforehand, or break it out into another function that uses enum.dayflag and enum.days. You might be able to go somewhere with the outline.


It could be aswell as the following code:

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

It goes inside if only when the enum value is contained on the value


All the answers work well with simple flags, you're probably going to get into issues when flags are combined.

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

probably need to add a check to test if the enum value it self is a combination. You'd probably need something like posted here by Henk van Boeijen to cover your requirement (you need to scroll down a bit)


You can do it directly by converting to int but you will loose type checking. I think the best way is use something similar to my proposition. It keep the proper type all the way. No conversion required. It is not perfect due to boxing which will add a little hit in performance.

Not perfect (boxing), but it does the job with no warning...

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

Usage:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }

참고URL : https://stackoverflow.com/questions/4171140/iterate-over-values-in-flags-enum

반응형