Якщо у мене є змінна, що містить перелік прапорів, чи можу я якось повторити значення бітів у цій конкретній змінній? Або мені потрібно використовувати 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())
{
...
}