Jika saya memiliki variabel yang memegang flag enum, dapatkah saya melakukan iterate pada nilai bit dalam variabel tertentu? Atau apakah saya harus menggunakan Enum.GetValues untuk beralih ke seluruh enum dan memeriksa yang ditetapkan?
Jika saya memiliki variabel yang memegang flag enum, dapatkah saya melakukan iterate pada nilai bit dalam variabel tertentu? Atau apakah saya harus menggunakan Enum.GetValues untuk beralih ke seluruh enum dan memeriksa yang ditetapkan?
Jawaban:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
tersedia dari .NET 4 dan seterusnya.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
myEnum.GetFLags()
static IEnumerable<Enum> GetFlags(this Enum input)
Berikut ini adalah solusi Linq untuk masalah tersebut.
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));
jika Anda memiliki nilai nol untuk diwakiliNone
Tidak ada metode builtin untuk mendapatkan setiap komponen sejauh yang saya tahu. Tapi di sini ada satu cara Anda bisa mendapatkannya:
[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
Saya mengadaptasi apa yang Enum
dilakukan secara internal untuk menghasilkan string sebagai gantinya mengembalikan bendera. Anda dapat melihat kode di reflektor dan harus kurang lebih setara. Bekerja dengan baik untuk kasus penggunaan umum di mana ada nilai yang mengandung banyak 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;
}
}
}
Metode ekstensi GetIndividualFlags()
mendapatkan semua bendera individu untuk suatu jenis. Jadi nilai yang mengandung banyak bit ditinggalkan.
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
dan Boo
bukannya adil Boo
.
Boo
(nilai kembali menggunakan ToString()
). Saya telah men-tweak untuk memungkinkan hanya bendera individu. Jadi, dalam contoh saya, Anda bisa mendapatkan Bar
dan Baz
bukannya Boo
.
Kembali pada hal ini beberapa tahun kemudian, dengan pengalaman yang sedikit lebih banyak, jawaban pamungkas saya hanya untuk nilai bit tunggal, bergerak dari bit terendah ke bit tertinggi, adalah sedikit variasi dari rutinitas dalam 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;
}
}
}
Tampaknya berfungsi, dan meskipun saya keberatan beberapa tahun yang lalu, saya menggunakan HasFlag di sini, karena jauh lebih terbaca daripada menggunakan perbandingan bitwise dan perbedaan kecepatan tidak signifikan untuk apa pun yang akan saya lakukan. (Sangat mungkin mereka telah meningkatkan kecepatan HasFlag sejak saat itu, untuk yang saya tahu ... saya belum menguji.)
yield return bits;
?
Keluar dari metode @ Greg, tetapi menambahkan fitur baru dari C # 7.3, Enum
batasannya:
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;
}
Batasan baru memungkinkan ini menjadi metode ekstensi, tanpa harus melalui (int)(object)e
, dan saya bisa menggunakan HasFlag
metode dan melemparkan langsung ke T
dari value
.
C # 7.3 juga menambahkan kendala untuk untuk delagate dan unmanaged
.
flags
parameternya menjadi tipe generik T
juga, jika tidak Anda harus secara eksplisit menentukan jenis enum setiap kali Anda menyebutnya.
+1 untuk jawaban yang diberikan oleh @ RobinHood70. Saya menemukan bahwa versi umum dari metode ini nyaman bagi saya.
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 dan +1 untuk @AustinWBryan untuk membawa C # 7.3 ke dalam ruang solusi.
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;
}
}
}
Anda tidak perlu mengulang semua nilai. cukup periksa flag spesifik Anda seperti:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
//do something...
}
atau (seperti pstrjds katakan dalam komentar) Anda dapat memeriksa untuk menggunakannya seperti:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
Apa yang saya lakukan adalah mengubah pendekatan saya, alih-alih mengetik parameter input dari metode sebagai enum
tipe, saya mengetiknya sebagai array dari enum
tipe ( MyEnum[] myEnums
), dengan cara ini saya hanya mengulang melalui array dengan pernyataan switch di dalam loop.
Tidak puas dengan jawaban di atas, meskipun itu awal.
Setelah menyatukan beberapa sumber berbeda di sini:
Poster sebelumnya di utas ini SO QnA
Code Proyek Enum Flags Cek Post
Great Enum <T> Utilitas
Saya membuat ini jadi biarkan saya tahu apa yang Anda pikirkan.
Parameter::
bool checkZero
memberitahukannya untuk memungkinkan 0
sebagai nilai flag. Secara default input = 0
mengembalikan kosong.
bool checkFlags
: mengatakannya untuk memeriksa apakah atribut Enum
tersebut didekorasi dengan [Flags]
atribut.
PS. Saya tidak punya waktu sekarang untuk mencari tahu checkCombinators = false
alg yang akan memaksanya untuk mengabaikan nilai enum yang merupakan kombinasi 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;
}
}
}
Ini memanfaatkan Enum Class Helper <T> yang ditemukan di sini yang saya perbarui untuk digunakan yield return
untuk 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);
}
}
Akhirnya, inilah contoh menggunakannya:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
Membangun berdasarkan jawaban Greg di atas, ini juga menangani kasus di mana Anda memiliki nilai 0 di enum Anda, seperti Tidak Ada = 0. Dalam hal ini, seharusnya tidak mengulangi nilai itu.
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;
}
Adakah yang tahu bagaimana meningkatkan ini lebih jauh sehingga dapat menangani case di mana semua flag di enum diatur dalam cara yang sangat cerdas yang dapat menangani semua tipe enum yang mendasarinya dan case All = ~ 0 dan All = EnumValue1 | EnumValue2 | EnumValue3 | ...
Anda dapat menggunakan Iterator dari Enum. Mulai dari kode 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];
}
}
}
}
Itu tidak diuji, jadi jika saya membuat kesalahan jangan ragu untuk memanggil saya. (jelas itu bukan masuk kembali.) Anda harus menetapkan nilai sebelumnya, atau memecahnya menjadi fungsi lain yang menggunakan enum.dayflag dan enum.days. Anda mungkin bisa pergi ke suatu tempat dengan garis besar.
Bisa juga kode berikut:
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(',');
}
Ini masuk ke dalam jika hanya ketika nilai enum terkandung pada nilai
Semua jawaban bekerja dengan baik dengan flag sederhana, Anda mungkin akan mendapatkan masalah ketika flag digabungkan.
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
mungkin perlu menambahkan cek untuk menguji apakah nilai enum itu sendiri adalah kombinasi. Anda mungkin perlu sesuatu seperti diposting di sini oleh Henk van Boeijen untuk memenuhi kebutuhan Anda (Anda perlu sedikit gulir ke bawah)
Metode ekstensi menggunakan batasan Enum dan generik baru untuk mencegah pengecoran:
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();
}
}
Anda dapat melakukannya secara langsung dengan mengonversi ke int tetapi Anda akan kehilangan pemeriksaan tipenya. Saya pikir cara terbaik adalah menggunakan sesuatu yang mirip dengan proposisi saya. Itu membuat tipe yang tepat sepanjang jalan. Tidak diperlukan konversi. Itu tidak sempurna karena tinju yang akan menambah sedikit hit dalam kinerja.
Tidak sempurna (tinju), tetapi ia melakukan pekerjaan tanpa peringatan ...
/// <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;
}
}
}
Pemakaian:
FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
foreach (FileAttributes fa in att.GetFlags())
{
...
}