플래그 열거 형을 보유하는 변수가있는 경우 어떻게 든 특정 변수의 비트 값을 반복 할 수 있습니까? 아니면 Enum.GetValues를 사용하여 전체 열거 형을 반복하고 어느 것이 설정되어 있는지 확인해야합니까?
플래그 열거 형을 보유하는 변수가있는 경우 어떻게 든 특정 변수의 비트 값을 반복 할 수 있습니까? 아니면 Enum.GetValues를 사용하여 전체 열거 형을 반복하고 어느 것이 설정되어 있는지 확인해야합니까?
답변:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
이후 .NET 4에서 사용할 수 있습니다.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
그러면 그냥 : myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
다음은 문제에 대한 Linq 솔루션입니다.
public static IEnumerable<Enum> GetFlags(this Enum e)
{
return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
.Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));
표시 할 값이 0 인 경우 사용None
내가 아는 한 각 구성 요소를 가져 오는 기본 제공 방법은 없습니다. 그러나 다음과 같은 방법으로 얻을 수 있습니다.
[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
.
Boo
(를 사용하여 반환 된 값)을 반환 ToString()
합니다. 개별 플래그 만 허용하도록 조정했습니다. 내 예에서 Bar
와 Baz
대신에 얻을 수 있습니다 Boo
.
몇 년 후 조금 더 많은 경험을 바탕으로 다시 돌아와서, 단일 비트 값에 대한 나의 궁극적 인 대답은 가장 낮은 비트에서 가장 높은 비트로 이동하는 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의 속도를 향상 시켰을 가능성이 있습니다. 아무것도 알지 못했지만 테스트하지 않았습니다.)
yield return bits;
?
@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
.
flags
매개 변수가 제네릭 형식 이기를 원했을 수도 T
있습니다. 그렇지 않으면 매번 열거 형을 명시 적으로 지정해야합니다.
@ 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...
}
비록 그들이 시작되었지만 위의 답변에 만족하지 못했습니다.
여기에 몇 가지 다른 소스를 조립할 후 :
이전 포스터이 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;
}
위의 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 | ...
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를 사용하는 다른 함수로 분류해야합니다. 당신은 개요와 어딘가에 갈 수 있습니다.
다음 코드와 같을 수 있습니다.
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(',');
}
열거 형 값이 값에 포함 된 경우에만 내부로 들어갑니다.
모든 답변은 간단한 플래그로 잘 작동하므로 플래그가 결합되면 문제가 발생할 수 있습니다.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
열거 형 값 자체가 조합인지 테스트하기 위해 검사를 추가해야 할 수도 있습니다. 귀하 의 요구 사항을 충족 시키기 위해 Henk van Boeijen 이 게시 한 것과 같은 것이 필요할 것입니다 (아래로 스크롤해야 함)
새로운 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();
}
}
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())
{
...
}