Bagaimana cara mengulang nilai dari Enum yang memiliki flag?

131

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?

Olivier Rogier
sumber
Jika Anda memiliki kendali atas API Anda, hindari menggunakan flag bit. Mereka jarang optimasi yang bermanfaat. Menggunakan struct dengan beberapa bidang 'bool' publik sama dengan semantik tetapi kode Anda secara dramatis lebih sederhana. Dan jika perlu nanti, Anda bisa mengubah bidang menjadi properti yang memanipulasi bidang bit secara internal, merangkum optimasi.
Jay Bazuzi
2
Saya mengerti apa yang Anda katakan, dan dalam banyak kasus itu masuk akal, tetapi dalam kasus ini, saya akan memiliki masalah yang sama dengan saran If ... Saya harus menulis pernyataan If untuk selusin bools yang berbeda sebagai gantinya menggunakan loop foreach sederhana atas array. (Dan karena ini adalah bagian dari DLL publik, saya tidak dapat melakukan hal-hal yang misterius seperti hanya memiliki array bools atau apa pun.)
10
"kode Anda secara dramatis lebih sederhana" - benar-benar kebalikannya jika Anda melakukan sesuatu selain hanya menguji bit individual ... loop dan mengatur operasi menjadi hampir mustahil karena bidang dan properti bukan entitas kelas satu.
Jim Balter
2
@nawfal "sedikit" Saya melihat apa yang Anda lakukan di sana
stannius

Jawaban:

179
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}
Greg
sumber
7
Catatan yang HasFlagtersedia dari .NET 4 dan seterusnya.
Andreas Grech
3
Ini bagus! Tetapi Anda dapat membuatnya lebih sederhana dan mudah digunakan. Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);myEnum.GetFLags()
Tetap
3
Satu garis bagus, josh, tetapi masih mengalami masalah mengambil nilai multi-flag (Boo) bukan hanya nilai-nilai single-flag (Bar, Baz), seperti dalam jawaban Jeff, di atas.
10
Nice - hati-hati terhadap None's - misalnya Items. Tidak ada dari jawaban Jeff yang akan selalu disertakan
Ilan
1
Metode tanda tangan harusstatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers
48

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);
}
agritton
sumber
7
Mengapa ini tidak ada di atas ?? :) Gunakan .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));jika Anda memiliki nilai nol untuk diwakiliNone
georgiosd
Sangat bersih. Solusi terbaik menurut saya.
Ryan Fiorini
@georgiosd Saya kira kinerjanya tidak terlalu bagus. (Tetapi harus cukup baik untuk sebagian besar tugas)
AntiHeadshot
41

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 Enumdilakukan 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
Jeff Mercado
sumber
Saya telah mempertimbangkan untuk melakukan split string, tapi itu mungkin lebih banyak overhead daripada hanya mengulangi nilai bit seluruh enum.
Sayangnya dengan melakukan itu, Anda harus menguji nilai yang berlebihan (jika tidak menginginkannya). Lihat contoh kedua saya, itu akan menghasilkan Bar, Bazdan Boobukannya adil Boo.
Jeff Mercado
Menarik bahwa Anda bisa mengeluarkan Boo dari itu, meskipun bagian itu tidak perlu (dan sebenarnya, ide yang sangat buruk :)) untuk apa yang saya lakukan.
Ini terlihat menarik, tetapi jika saya tidak salah paham, itu akan mengembalikan Boo untuk enum di atas, di mana saya ingin menyebutkan hanya versi yang bukan kombinasi dari nilai-nilai lain (yaitu, yang merupakan kekuatan dua) . Bisakah itu dilakukan dengan mudah? Saya sudah mencoba dan saya tidak bisa memikirkan cara mudah untuk mengidentifikasi itu tanpa menggunakan matematika FP.
@Robin: Anda benar, yang asli akan kembali Boo(nilai kembali menggunakan ToString()). Saya telah men-tweak untuk memungkinkan hanya bendera individu. Jadi, dalam contoh saya, Anda bisa mendapatkan Bardan Bazbukannya Boo.
Jeff Mercado
26

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.)


sumber
Hanya sebuah note note diinisialisasi ke int harus ke ulong seperti bit, harus diinisialisasi sebagai 1ul
forcewill
Terima kasih, saya akan memperbaikinya! (Saya baru saja pergi dan memeriksa kode saya yang sebenarnya dan saya sudah memperbaikinya sebaliknya dengan secara khusus menyatakannya sebagai ulong.)
2
Ini adalah satu-satunya solusi yang saya temukan yang tampaknya juga tidak menderita dari kenyataan bahwa jika Anda memiliki bendera dengan nilai nol, yang harus mewakili "Tidak ada", jawaban lain metode GetFlag () akan mengembalikan YourEnum Anda. Tidak ada sebagai salah satu bendera bahkan jika itu tidak benar-benar di enum Anda menjalankan metode! Saya mendapatkan entri log duplikat aneh karena metode berjalan lebih dari yang saya harapkan ketika mereka hanya memiliki satu set flag enum yang tidak nol. Terima kasih telah meluangkan waktu untuk memperbarui dan menambahkan solusi hebat ini!
BrianH
yield return bits;?
Jaider
1
Saya ingin variabel "Semua" enum, yang saya tetapkan ulong.MaxValue, jadi semua bit diatur ke '1'. Tetapi kode Anda mengenai loop tak terbatas, karena flag <bits tidak pernah mengevaluasi ke true (flag loop ke negatif dan kemudian macet di 0).
Haighstrom
15

Keluar dari metode @ Greg, tetapi menambahkan fitur baru dari C # 7.3, Enumbatasannya:

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 HasFlagmetode dan melemparkan langsung ke Tdari value.

C # 7.3 juga menambahkan kendala untuk untuk delagate dan unmanaged.

AustinWBryan
sumber
4
Anda mungkin ingin flagsparameternya menjadi tipe generik Tjuga, jika tidak Anda harus secara eksplisit menentukan jenis enum setiap kali Anda menyebutnya.
Ray
11

+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;
        }
    }
}
Wallace Kelly
sumber
3

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...
}
Dr TJ
sumber
5
Jika Anda menggunakan .Net 4.0 ada metode ekstensi HasFlag yang dapat Anda gunakan untuk melakukan hal yang sama: myVar.HasFlag (FlagsEnum.Flag1)
pstrjds
1
Jika seorang programmer tidak dapat memahami bitwise DAN operasi mereka harus mengemasnya dan mencari karier baru.
Ed S.
2
@ Ed: benar, tetapi HasFlag lebih baik ketika Anda membaca kode lagi ... (mungkin setelah beberapa bulan atau tahun)
Dr TJ
4
@ Ed Swangren: Ini benar-benar tentang membuat kode lebih mudah dibaca dan kurang bertele-tele, belum tentu karena menggunakan operasi bitwise adalah "sulit."
Jeff Mercado
2
HasFlag sangat lambat. Coba loop besar menggunakan HasFlag vs bit-masking dan Anda akan melihat perbedaan besar.
3

Apa yang saya lakukan adalah mengubah pendekatan saya, alih-alih mengetik parameter input dari metode sebagai enumtipe, saya mengetiknya sebagai array dari enumtipe ( MyEnum[] myEnums), dengan cara ini saya hanya mengulang melalui array dengan pernyataan switch di dalam loop.

Ragin'Geek
sumber
2

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 checkZeromemberitahukannya untuk memungkinkan 0sebagai nilai flag. Secara default input = 0mengembalikan kosong.
bool checkFlags: mengatakannya untuk memeriksa apakah atribut Enumtersebut didekorasi dengan [Flags]atribut.
PS. Saya tidak punya waktu sekarang untuk mencari tahu checkCombinators = falsealg 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 returnuntuk 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;
    }
eudaimos
sumber
Maaf, belum sempat melihat proyek ini dalam beberapa hari. Saya akan menghubungi Anda setelah saya melihat kode Anda dengan lebih baik.
4
Hanya kepala bahwa ada bug dalam kode itu. Kode di kedua cabang pernyataan if (checkCombinators) identik. Juga, mungkin bukan bug, tetapi tidak terduga, adalah bahwa jika Anda memiliki nilai enum yang dinyatakan untuk 0, itu akan selalu dikembalikan dalam koleksi. Tampaknya hanya boleh dikembalikan jika checkZero benar dan tidak ada flag lain yang ditetapkan.
dhochee
@dochee. Saya setuju. Atau kodenya cukup bagus, tetapi argumennya membingungkan.
SETELAH
2

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 | ...

Didier A.
sumber
1

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.

SilverbackNet
sumber
0

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

G. Manucci
sumber
0

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)

Walter Vehoeven
sumber
0

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();
    }
}
Saeb Amini
sumber
-1

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())
    {
        ...
    }
Eric Ouellet
sumber