Se ho una variabile che contiene un enum di flag, posso in qualche modo scorrere i valori dei bit in quella specifica variabile? O devo usare Enum.GetValues per scorrere su tutto l'enum e verificare quali sono impostati?
Se ho una variabile che contiene un enum di flag, posso in qualche modo scorrere i valori dei bit in quella specifica variabile? O devo usare Enum.GetValues per scorrere su tutto l'enum e verificare quali sono impostati?
Risposte:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
è disponibile da .NET 4 in poi.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
Quindi solo: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
Ecco una soluzione Linq al problema.
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));
se hai un valore zero da rappresentareNone
Non ci sono metodi integrati per ottenere ogni componente per quanto ne so. Ma ecco un modo per ottenerli:
[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
Ho adattato ciò che Enum
fa internamente per generare la stringa per restituire invece i flag. Puoi guardare il codice in reflector e dovrebbe essere più o meno equivalente. Funziona bene per casi di utilizzo generale in cui sono presenti valori che contengono più bit.
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;
}
}
}
Il metodo di estensione GetIndividualFlags()
ottiene tutti i singoli flag per un tipo. Quindi i valori contenenti più bit vengono esclusi.
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
e Boo
non solo Boo
.
Boo
(il valore restituito usando ToString()
). L'ho modificato per consentire solo singole bandiere. Quindi nel mio esempio, puoi ottenere Bar
e Baz
invece di Boo
.
Tornando a questo qualche anno dopo, con un po 'più di esperienza, la mia risposta definitiva solo ai valori a bit singolo, passando dal bit più basso al bit più alto, è una leggera variante della routine interiore di 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;
}
}
}
Sembra funzionare e, nonostante le mie obiezioni di alcuni anni fa, uso qui HasFlag, poiché è molto più leggibile rispetto all'utilizzo di confronti bit a bit e la differenza di velocità è insignificante per qualsiasi cosa che farò. (È del tutto possibile che da allora abbiano migliorato la velocità di HasFlags, per quel che ne so ... non ho ancora testato.)
yield return bits;
?
Uscendo dal metodo di @ Greg, ma aggiungendo una nuova funzionalità da C # 7.3, il Enum
vincolo:
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;
}
Il nuovo vincolo permette a questo di essere un metodo di estensione, senza dover passare attraverso (int)(object)e
, e posso usare il HasFlag
metodo e il cast direttamente T
davalue
.
C # 7.3 ha anche aggiunto vincoli a per i delagati e unmanaged
.
flags
parametro fosse di tipo generico T
, altrimenti dovresti specificare esplicitamente il tipo enum ogni volta che lo chiami.
+1 per la risposta fornita da @ RobinHood70. Ho scoperto che una versione generica del metodo era conveniente per me.
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 E +1 per @AustinWBryan per aver portato C # 7.3 nello spazio della soluzione.
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;
}
}
}
Non è necessario ripetere tutti i valori. controlla le tue bandiere specifiche in questo modo:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
//do something...
}
oppure (come diceva pstrjds nei commenti) puoi controllare per usarlo come:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
Quello che ho fatto è stato cambiare il mio approccio, invece di digitare il parametro di input del metodo come enum
tipo, l'ho digitato come un array di enum
tipo ( MyEnum[] myEnums
), in questo modo ho solo iterato l'array con un'istruzione switch all'interno del ciclo.
Non ero soddisfatto delle risposte sopra, anche se erano l'inizio.
Dopo aver messo insieme alcune fonti diverse qui:
Poster precedente nel
codice SO QnA di questa discussione Progetto Enum Flags Check Post
Great Enum <T> Utilità
L'ho creato, quindi fammi sapere cosa ne pensi.
Parametri::
bool checkZero
indica di consentire 0
come valore flag. Per impostazione predefinita, input = 0
restituisce vuoto.
bool checkFlags
: gli dice di controllare se Enum
è decorato con l' [Flags]
attributo.
PS. Non ho tempo in questo momento per capire l' checkCombinators = false
alg che lo costringerà a ignorare i valori enum che sono combinazioni di bit.
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;
}
}
}
Questo utilizza la classe Helper <T> trovata qui che ho aggiornato per usare yield return
per 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);
}
}
Infine, ecco un esempio di utilizzo:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
Basandosi sulla risposta di Greg sopra, questo si occupa anche del caso in cui hai un valore 0 nel tuo enum, come None = 0. In tal caso, non dovrebbe iterare su quel valore.
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;
}
Qualcuno saprebbe come migliorare ulteriormente questo in modo da poter gestire il caso in cui tutte le bandiere nell'enum sono impostate in un modo super intelligente in grado di gestire tutto il tipo di enum sottostante e il caso di All = ~ 0 e All = EnumValue1 | EnumValue2 | EnumValue3 | ...
Puoi usare un Iteratore dall'Enum. A partire dal codice 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];
}
}
}
}
Non è testato, quindi se ho fatto un errore sentiti libero di chiamarmi. (ovviamente non rientra nuovamente.) Dovresti assegnare prima il valore o suddividerlo in un'altra funzione che utilizza enum.dayflag ed enum.days. Potresti essere in grado di andare da qualche parte con il contorno.
Potrebbe essere anche il seguente codice:
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(',');
}
Va dentro se solo quando il valore enum è contenuto nel valore
Tutte le risposte funzionano bene con semplici flag, probabilmente ti imbatterai in problemi quando i flag vengono combinati.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
probabilmente è necessario aggiungere un segno di spunta per verificare se il valore enum stesso è una combinazione. Probabilmente avresti bisogno di qualcosa come pubblicato qui da Henk van Boeijen per soddisfare le tue esigenze (devi scorrere un po 'verso il basso)
Metodo di estensione utilizzando il nuovo vincolo Enum e generici per impedire il casting:
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();
}
}
Puoi farlo direttamente convertendolo in int ma perderai il controllo del tipo. Penso che il modo migliore sia usare qualcosa di simile alla mia proposta. Mantiene il tipo giusto fino in fondo. Nessuna conversione richiesta. Non è perfetto a causa della boxe che aggiungerà un piccolo successo nelle prestazioni.
Non perfetto (boxe), ma fa il lavoro senza preavviso ...
/// <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;
}
}
}
Uso:
FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
foreach (FileAttributes fa in att.GetFlags())
{
...
}