Якщо у мене є змінна, що містить перелік прапорів, чи можу я якось повторити значення бітів у цій конкретній змінній? Або мені потрібно використовувати 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));
якщо у вас є нульове значення для представлення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
.
Повернувшись до цього через кілька років, маючи трохи більше досвіду, моя остаточна відповідь лише для однобітних значень, рухаючись від найнижчого біта до найвищого біта, є незначним варіантом внутрішньої рутини Джеффа Меркадо:
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;
?
Вимкнення методу @ Грега, але додавання нової функції від 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
, інакше вам доведеться чітко вказувати тип enum кожного разу, коли ви його викликаєте.
+1 за відповідь, надану @ RobinHood70. Я виявив, що загальна версія методу була для мене зручною.
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;
}
}
}
EDIT І +1 для @AustinWBryan для введення C # 7.3 у простір рішення.
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...
}
Що я зробив, це змінити мій підхід, замість того, щоб вводити вхідний параметр методу як enum
тип, я набрав його як масив enum
типу ( MyEnum[] myEnums
), таким чином я просто повторюю масив із заявою перемикання всередині циклу.
Не були задоволені відповідями вище, хоча вони були початком.
Після зібрання сюди кількох різних джерел тут:
Попередній плакат у проекті поточного
коду SO QnA прапори Enum прапори Перевірити пост
Great Enum <T> Утиліта
Я створив це, тому дайте мені знати, що ви думаєте.
Параметри::
bool checkZero
вказує на те, щоб дозволити 0
як значення прапора. За замовчуванням input = 0
повертається порожнім.
bool checkFlags
: каже, щоб перевірити, чи Enum
оформлено w / [Flags]
атрибут.
PS. Зараз я не 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 <Hel> Class Helper <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;
}
Спираючись на відповідь Грега вище, це також стосується випадку, коли у вашому перерахунку є значення 0, наприклад None = 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. Починаючи з коду 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,
}
ймовірно, потрібно додати чек, щоб перевірити, чи є значення перерахунку значення "self" - комбінація. Вам, напевно, знадобиться щось на кшталт опублікованого тут Хенка ван Бойєна, щоб покрити свою вимогу (потрібно трохи прокрутити вниз)
Метод розширення з використанням нового обмеження та дженериків 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())
{
...
}