Jeśli mam zmienną zawierającą wyliczenie flag, czy mogę w jakiś sposób iterować po wartościach jednobitowych w tej określonej zmiennej? Czy muszę użyć Enum.GetValues do iteracji po całym wyliczeniu i sprawdzić, które z nich są ustawione?
Jeśli mam zmienną zawierającą wyliczenie flag, czy mogę w jakiś sposób iterować po wartościach jednobitowych w tej określonej zmiennej? Czy muszę użyć Enum.GetValues do iteracji po całym wyliczeniu i sprawdzić, które z nich są ustawione?
Odpowiedzi:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
jest dostępny od .NET 4 i nowszych.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
Then just: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
Oto rozwiązanie tego problemu firmy 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));
jeśli masz wartość zero do przedstawieniaNone
O ile wiem, nie ma żadnych wbudowanych metod pobierania każdego komponentu. Ale oto jeden sposób, w jaki możesz je zdobyć:
[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
Dostosowałem to, co Enum
robi wewnętrznie, aby wygenerować ciąg zamiast zwracać flagi. Możesz spojrzeć na kod w reflektorze i powinien być mniej więcej równoważny. Działa dobrze w ogólnych przypadkach użycia, w których istnieją wartości zawierające wiele bitów.
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;
}
}
}
Metoda rozszerzenia GetIndividualFlags()
pobiera wszystkie indywidualne flagi dla typu. Tak więc wartości zawierające wiele bitów są pomijane.
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
a Boo
zamiast po prostu Boo
.
Boo
(wartość zwrócona za pomocą ToString()
). Poprawiłem go, aby zezwalał tylko na pojedyncze flagi. Więc w moim przykładzie możesz uzyskać Bar
i Baz
zamiast Boo
.
Wracając do tego kilka lat później, z nieco większym doświadczeniem, moją ostateczną odpowiedzią tylko dla wartości jednobitowych, przechodząc od najniższego do najwyższego bitu, jest niewielki wariant wewnętrznej rutyny Jeffa 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;
}
}
}
Wydaje się, że działa i pomimo moich zastrzeżeń sprzed kilku lat używam tutaj HasFlag, ponieważ jest on znacznie bardziej czytelny niż przy użyciu porównań bitowych, a różnica prędkości jest nieistotna dla wszystkiego, co będę robił. (Jest całkiem możliwe, że i tak poprawili prędkość HasFlags od tego czasu, z tego, co wiem ... nie testowałem.)
yield return bits;
?
Wyjście z metody @ Grega, ale dodanie nowej funkcji z C # 7.3, Enum
ograniczenie:
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;
}
Nowe ograniczenie pozwala na to, aby była to metoda rozszerzająca, bez konieczności rzutowania (int)(object)e
, i mogę używać tej HasFlag
metody i rzutować bezpośrednio T
z value
.
C # 7,3 dodał również ograniczenia dla delagates i unmanaged
.
flags
parametr był również typu ogólnego T
, w przeciwnym razie musiałbyś jawnie określać typ wyliczenia za każdym razem, gdy go wywołujesz.
+1 za odpowiedź udzieloną przez @ RobinHood70. Odkryłem, że ogólna wersja metody jest dla mnie wygodna.
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;
}
}
}
EDYCJA I +1 dla @AustinWBryan za wprowadzenie języka C # 7.3 do przestrzeni rozwiązań.
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;
}
}
}
Nie musisz iterować wszystkich wartości. po prostu sprawdź swoje określone flagi w ten sposób:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
//do something...
}
lub (jak powiedział pstrjds w komentarzach) możesz sprawdzić, czy go używasz, jak:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
Zmieniłem swoje podejście, zamiast wpisywać parametr wejściowy metody jako enum
typ, wpisałem go jako tablicę enum
typu type ( MyEnum[] myEnums
), w ten sposób po prostu iteruję przez tablicę z instrukcją switch wewnątrz pętli.
Nie był zadowolony z odpowiedzi powyżej, chociaż były one początkiem.
Po zebraniu razem kilku różnych źródeł tutaj:
Poprzedni plakat w tym wątku SO QnA
Code Project Enum Flags Check Post
Great Enum <T> Utility
Stworzyłem to, więc daj mi znać, co myślisz.
Parametry::
bool checkZero
mówi mu, aby zezwolić 0
jako wartość flagi. Domyślnie input = 0
zwraca puste.
bool checkFlags
: każe mu sprawdzić, czy Enum
jest ozdobiony [Flags]
atrybutem.
PS. Nie mam teraz czasu, aby wymyślić checkCombinators = false
algorytm, który zmusi go do zignorowania wartości wyliczenia, które są kombinacjami bitów.
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;
}
}
}
To wykorzystuje pomocniczą klasę Enum <T> znalezioną tutaj , którą zaktualizowałem do użycia yield return
dla 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);
}
}
Na koniec, oto przykład jego użycia:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
Opierając się na powyższej odpowiedzi Grega, rozwiązuje to również przypadek, w którym masz wartość 0 w swoim wyliczeniu, na przykład None = 0. W takim przypadku nie powinno iterować po tej wartości.
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;
}
Czy ktoś wiedziałby, jak to jeszcze bardziej ulepszyć, aby poradził sobie z przypadkiem, w którym wszystkie flagi w wyliczeniu są ustawione w super inteligentny sposób, który mógłby obsłużyć wszystkie podstawowe typy wyliczeń i przypadek All = ~ 0 i All = EnumValue1 | EnumValue2 | EnumValue3 | ...
Możesz użyć Iteratora z Enum. Począwszy od kodu 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];
}
}
}
}
Nie jest testowany, więc jeśli popełniłem błąd, zadzwoń do mnie. (oczywiście nie jest to ponowne wejście). Musiałbyś wcześniej przypisać wartość lub podzielić ją na inną funkcję, która używa enum.dayflag i enum.days. Możesz być w stanie pójść gdzieś z konspektem.
Metoda rozszerzenia przy użyciu nowego ograniczenia wyliczenia i typów ogólnych, aby zapobiec rzutowaniu:
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();
}
}
Może to być również następujący kod:
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(',');
}
Wchodzi do środka tylko wtedy, gdy wartość wyliczenia jest zawarta w wartości
Wszystkie odpowiedzi działają dobrze z prostymi flagami, prawdopodobnie będziesz mieć problemy z połączeniem flag.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
prawdopodobnie trzeba będzie dodać sprawdzenie, aby sprawdzić, czy wartość wyliczenia jest kombinacją. Prawdopodobnie potrzebowałbyś czegoś takiego, jak opublikował tutaj Henk van Boeijen, aby spełnić swoje wymagania (musisz trochę przewinąć)
Kontynuując moje wysiłki, aby skrócić kod, jest to moja najnowsza wersja procedury. (Jestem OP ... długa historia). Jak wspomniano wcześniej, ignoruje to wartości None i wielobitowe.
Zauważ, że używa to ograniczenia Enum i wzorca var, więc będzie wymagało co najmniej C # 7,3.
public static IEnumerable<T> GetUniqueFlags<T>(this T value)
where T : Enum
{
var valueLong = Convert.ToUInt64(value, CultureInfo.InvariantCulture);
foreach (var enumValue in value.GetType().GetEnumValues())
{
if (
enumValue is T flag // cast enumValue to T
&& Convert.ToUInt64(flag, CultureInfo.InvariantCulture) is var bitValue // convert flag to ulong
&& (bitValue & (bitValue - 1)) == 0 // is this a single-bit value?
&& (valueLong & bitValue) != 0 // is the bit set?
)
{
yield return flag;
}
}
}
GetEnumValues()
na T[]
i wyeliminować enumValue is T flag
warunek, ale polega to na tym, że GetEnumValues zawsze zwraca tablicę typu T, co nie jest wymagane w umowie.
Możesz to zrobić bezpośrednio, konwertując na int, ale stracisz sprawdzanie typów. Myślę, że najlepszym sposobem jest użycie czegoś podobnego do mojej propozycji. Zachowuje właściwy typ przez cały czas. Nie jest wymagana konwersja. Nie jest doskonały ze względu na boks, który doda trochę uderzenia w wydajność.
Nie doskonały (boks), ale spełnia swoje zadanie bez ostrzeżenia ...
/// <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;
}
}
}
Stosowanie:
FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
foreach (FileAttributes fa in att.GetFlags())
{
...
}