Bagaimana cara memeriksa apakah ada flag kombinasi bendera yang ditetapkan?

180

Katakanlah saya punya enum ini:

[Flags]
enum Letters
{
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = A | B | C,
}

Untuk memeriksa apakah misalnya ABdiatur saya bisa melakukan ini:

if((letter & Letters.AB) == Letters.AB)

Apakah ada cara yang lebih sederhana untuk memeriksa apakah ada flag dari gabungan flag konstan yang ditetapkan daripada yang berikut?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Bisakah Anda misalnya menukar &dengan sesuatu?

Tidak terlalu stabil dalam hal biner seperti ini ...

Svish
sumber
Seharusnya tidak semua membaca 'All = A | B | C '?
stevehipwell
4
AB | C setara dengan A | B | C karena AB didefinisikan sebagai A | B sebelumnya.
Daniel Brückner
1
@Daniel Brückner - Ini setara, tetapi kurang dapat dibaca. Apalagi jika enum itu diperluas.
stevehipwell
Benar. Saya bisa mengubahnya untuk bacaan yang lebih baik.
Svish

Jawaban:

145

Jika Anda ingin tahu apakah huruf memiliki salah satu huruf di AB, Anda harus menggunakan DAN & operator. Sesuatu seperti:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}
yeyeyerman
sumber
2
Sejauh yang saya bisa lihat, ini berhasil. Dan punya komentar paling jelas. Tidak dapat dikompilasi tanpa tanda kurung letter & Letters.AB. Diedit di sana.
Svish
Juga jika saya memperkenalkan Letters.None, saya berasumsi Anda bisa menukar itu dengan tampilan 0yang kurang bisa dibandingkan dengan angka ajaib?
Svish
Tentu saja. Tapi saya tidak berpikir perbandingan AND dengan 0 dapat dianggap sebagai angka ajaib secara ketat.
yeyeyerman
9
juga stackoverflow.com/questions/40211/how-to-compare-flags-in-c adalah jawaban yang disarankan karena memeriksa item yang dipertanyakan yang bertentangan dengan memeriksa apakah itu sama dengan 0
dan richards
@ danrichardson masalah dengan cek untuk item yang tepat adalah bahwa ia menghilangkan kasus ketika bagian dari nilai majemuk diatur (baik A, atau B), yang bukan apa yang diinginkan OP.
Tom Lint
181

Di .NET 4 Anda dapat menggunakan metode Enum.HasFlag :

using System;

[Flags] public enum Pet {
   None = 0,
   Dog = 1,
   Cat = 2,
   Bird = 4,
   Rabbit = 8,
   Other = 16
}

public class Example
{
   public static void Main()
   {
      // Define three families: one without pets, one with dog + cat and one with a dog only
      Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog };
      int familiesWithoutPets = 0;
      int familiesWithDog = 0;

      foreach (Pet petsInFamily in petsInFamilies)
      {
         // Count families that have no pets. 
         if (petsInFamily.Equals(Pet.None))
            familiesWithoutPets++;
         // Of families with pets, count families that have a dog. 
         else if (petsInFamily.HasFlag(Pet.Dog))
            familiesWithDog++;
      }
      Console.WriteLine("{0} of {1} families in the sample have no pets.", 
                        familiesWithoutPets, petsInFamilies.Length);   
      Console.WriteLine("{0} of {1} families in the sample have a dog.", 
                        familiesWithDog, petsInFamilies.Length);   
   }
}

Contoh ini menampilkan output berikut:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.
Chuck Kasabula
sumber
14
Ini tidak menjawab pertanyaan OP. Anda harus && beberapa operasi HasFlag untuk menentukan apakah ada flag yang ditetapkan. Jadi pertanyaannya adalah apakah petsInFamilymemiliki Pet.Dog || Pet.Cat?
GoClimbColorado
1
Lihat jawaban jelas Tn. Skeet ... HasFlags Multiple
GoClimbColorado
59

Saya menggunakan metode ekstensi untuk menulis hal-hal seperti itu:

if (letter.IsFlagSet(Letter.AB))
    ...

Berikut kodenya:

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(true);
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static string GetDescription<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(false);
        string name = Enum.GetName(typeof(T), value);
        if (name != null)
        {
            FieldInfo field = typeof(T).GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}
Thomas Levesque
sumber
1
Anda bisa membuatnya menjadi sedikit lebih ketat seperti: where T : struct, IConvertible. Kode yang bagus!
Hamish Grubijan
@HamishGrubijan, poin bagus ... dan enum juga mengimplementasikan IFormattable dan IComparable. Namun, semua tipe numerik mengimplementasikan antarmuka itu juga, jadi tidak cukup untuk mengecualikannya
Thomas Levesque
Terima kasih telah berbagi tetapi Anda tidak selalu perlu memeriksa enum. IsFlagSet(this Enum value, Enum flag)Cukup.
djmj
34

Ada metode HasFlag di .NET 4 atau lebih tinggi.

if(letter.HasFlag(Letters.AB))
{
}
Artru
sumber
26

Jika Anda dapat menggunakan .NET 4 atau lebih tinggi daripada menggunakan metode HasFlag ()

contoh

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set

sama dengan

letter.HasFlag(Letters.AB)
Luka
sumber
Apakah Anda yakin bitwise ORmembuatnya "keduanya harus diatur" dan bukan?
Kurung
1
bitwise ORakan menggabungkan nilai, jadi 1000 | 0010 menjadi 1010, atau keduanya disetel
Armando
13

Jika itu benar-benar mengganggu Anda, Anda dapat menulis fungsi seperti itu:

public bool IsSet(Letters value, Letters flag)
{
    return (value & flag) == flag;
}

if (IsSet(letter, Letters.A))
{
   // ...
}

// If you want to check if BOTH Letters.A and Letters.B are set:
if (IsSet(letter, Letters.A & Letters.B))
{
   // ...
}

// If you want an OR, I'm afraid you will have to be more verbose:
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B))
{
   // ...
}
Tamas Czinege
sumber
1
Baris return (value & flag) == flag;tidak mengkompilasi: "Operator '&' tidak dapat diterapkan ke operan tipe 'T' dan 'T'" .
Fredrik Mörk
1
kagum: Pertanyaannya bukan tentang operasi biner, pertanyaannya adalah tentang menyederhanakan sintaks dari operasi terkait bitmask di C #. Ada banyak pertanyaan dan jawaban terkait operasi biner yang sangat baik pada stackoverflow, tidak perlu memposting ulang di mana-mana.
Tamas Czinege,
Saya harus merekomendasikan bahwa mereka yang tidak terbiasa dengan operasi biner menjadi akrab, karena perancah untuk menyembunyikannya di atas sebenarnya membuat hal-hal menjadi jauh lebih mudah dibaca menurut pendapat saya. Tentu saja saya 'mentah' solusi di bawah ini saat ini tidak melakukannya dengan baik dibandingkan dengan nilai dari solusi ini, sehingga orang-orang yang memberikan suara preferensi mereka dan saya harus menghormati itu ;-)
Will
10

Untuk memeriksa apakah misalnya AB diatur, saya dapat melakukan ini:

if ((letter & Letters.AB) == Letters.AB)

Apakah ada cara yang lebih sederhana untuk memeriksa apakah ada flag dari gabungan flag konstan yang ditetapkan daripada yang berikut?

Ini cek yang kedua A dan B ditetapkan, dan pengabaian apakah ada bendera lain yang ditetapkan.

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Ini memeriksa apakah A atau B diatur, dan mengabaikan apakah ada flag lain yang diatur atau tidak.

Ini dapat disederhanakan untuk:

if(letter & Letters.AB)

Inilah C untuk operasi biner; harus langsung menerapkan ini ke C #:

enum {
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = AB | C,
};

int flags = A|C;

bool anything_and_a = flags & A;

bool only_a = (flags == A);

bool a_and_or_c_and_anything_else = flags & (A|C);

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C);

bool only_a_and_c = (flags == (A|C));

Secara kebetulan, penamaan variabel dalam contoh pertanyaan adalah 'huruf' tunggal, yang mungkin menyiratkan bahwa itu hanya mewakili satu huruf; kode contoh memperjelas bahwa set huruf yang mungkin dan beberapa nilai diperbolehkan, jadi pertimbangkan untuk mengganti nama variabel 'huruf'.

Akan
sumber
Tidak anything_and_a, a_and_or_c_and_anything_elsedan both_ac_and_anything_elseselalu benar? atau saya kehilangan sesuatu di sini?
Svish
Dalam hal ini, Anda dapat melihat bendera mana yang telah diinisialisasi. Namun, jika flag tidak mengandung A, maka (flag & A) akan menjadi 0, yang salah. both_ac_and_anything_else memastikan bahwa A dan C diatur, tetapi mengabaikan flag lain yang juga disetel (misalnya benar apakah B diatur atau tidak).
Will
Hm, beberapa dari mereka berakhir sebagai angka dan bukan boolean di C # sekalipun. Bagaimana Anda mengubahnya menjadi boolean?
Svish
Ini tidak secara implisit dikonversi untuk Anda? Nol sama dengan 'salah', dan semua nilai lain adalah 'benar'.
Will
4

Bagaimana tentang

if ((letter & Letters.AB) > 0)

?

Jakob Christensen
sumber
Iya! Ini akan memfilter nilai A dan B, dan mengabaikan jika C disertakan. Jadi jika itu adalah> 0, itu juga A atau B atau AB.
kagum
3
Ini tidak 100% berfungsi dengan nilai yang ditandatangani. ! = 0 lebih baik dari> 0 karena alasan ini.
stevehipwell
4

Saya membuat metode ekstensi sederhana yang tidak perlu dicentang Enumjenisnya:

public static bool HasAnyFlag(this Enum value, Enum flags)
{
    return
        value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0);
}

Ini juga berfungsi pada enum yang dapat dibatalkan. HasFlagMetode standar tidak, jadi saya membuat ekstensi untuk membahasnya juga.

public static bool HasFlag(this Enum value, Enum flags)
{
    int f = Convert.ToInt32(flags);

    return
        value != null && ((Convert.ToInt32(value) & f) == f);
}

Tes sederhana:

[Flags]
enum Option
{
    None = 0x00,
    One = 0x01,
    Two = 0x02,
    Three = One | Two,
    Four = 0x04
}

[TestMethod]
public void HasAnyFlag()
{
    Option o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

[TestMethod]
public void HasAnyFlag_NullableEnum()
{
    Option? o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

Nikmati!

Henk van Boeijen
sumber
4

Ada banyak jawaban di sini tapi saya pikir cara paling idiomatis untuk melakukan ini dengan Flags adalah Letters.AB.HasFlag (letter) atau (Letters.A | Letters.B) .HasFlag (letter) jika Anda tidak sudah memiliki Letters.AB. letter.HasFlag (Letters.AB) hanya berfungsi jika memiliki keduanya.

Novaterata
sumber
3

Apakah ini akan berhasil untuk Anda?

if ((letter & (Letters.A | Letters.B)) != 0)

Salam,

Sebastiaan

Sebastiaan M
sumber
1

Anda dapat menggunakan metode ekstensi ini pada enum, untuk semua jenis enum:

public static bool IsSingle(this Enum value)
{
    var items = Enum.GetValues(value.GetType());
    var counter = 0;
    foreach (var item in items)
    {
        if (value.HasFlag((Enum)item))
        {
            counter++;
        }
        if (counter > 1)
        {
            return false;
        }
    }
    return true;
}
masehhat
sumber
0
if((int)letter != 0) { }
Lee
sumber
Anda mungkin kesalahan yang sama seperti yang saya lakukan - dia ingin memeriksa apakah A atau B diatur tetapi abaikan C.
Daniel Brückner
Anda tidak perlu para pemeran jika Anda memeriksa enum terhadap 0.
stevehipwell
Ini akan memeriksa apakah salah satu dari mereka ditetapkan, tidak jika ada enum gabungan ditetapkan.
Svish
0

Anda bisa memeriksa apakah nilainya tidak nol.

if ((Int32)(letter & Letters.AB) != 0) { }

Tetapi saya akan menganggapnya sebagai solusi yang lebih baik untuk memperkenalkan nilai enumerasi baru dengan nilai nol dan membandingkannya dengan nilai enumerasi ini (jika mungkin karena Anda harus dapat memodifikasi enumerasi).

[Flags]
enum Letters
{
    None = 0,
    A    = 1,
    B    = 2,
    C    = 4,
    AB   =  A | B,
    All  = AB | C
}

if (letter != Letters.None) { }

MEMPERBARUI

Salah membaca pertanyaan - perbaiki saran pertama dan abaikan saja saran kedua.

Daniel Brückner
sumber
Anda tidak perlu para pemeran jika Anda memeriksa enum terhadap 0.
stevehipwell
0

Ada dua pendekatan yang dapat saya lihat yang akan berfungsi untuk memeriksa bit yang diset.

Aproach A

if (letter != 0)
{
}

Ini berfungsi selama Anda tidak keberatan memeriksa semuanya bit, termasuk yang tidak didefinisikan juga!

Aproach B

if ((letter & Letters.All) != 0)
{
}

Ini hanya memeriksa bit yang ditentukan, selama Letters. Semua mewakili semua bit yang mungkin.

Untuk bit tertentu (satu set atau lebih), gunakan Aproach B mengganti Letters. Semua dengan bit yang ingin Anda periksa (lihat di bawah).

if ((letter & Letters.AB) != 0)
{
}
stevehipwell
sumber
Anda mungkin kesalahan yang sama seperti yang saya lakukan - dia ingin memeriksa apakah A atau B diatur tetapi abaikan C.
Daniel Brückner
-1

Maaf, tetapi saya akan menunjukkannya di VB :)

   <Flags()> Public Enum Cnt As Integer
        None = 0
        One = 1
        Two = 2
        Three = 4
        Four = 8    
    End Enum

    Sub Test()
    Dim CntValue As New Cnt
    CntValue += Cnt.One
    CntValue += Cnt.Three
    Console.WriteLine(CntValue)
    End Sub

CntValue = 5 Jadi enum berisi 1 + 4

Wiroko
sumber