Bagaimana Membandingkan Bendera dalam C #?

155

Saya memiliki bendera enum di bawah ini.

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

Saya tidak bisa membuat pernyataan if mengevaluasi ke true.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Bagaimana saya bisa mewujudkannya?

David Basarab
sumber
Perbaiki saya jika saya salah, apakah 0 pantas untuk digunakan sebagai nilai bendera?
Roy Lee
4
@Roylee: 0 dapat diterima, dan ide yang baik untuk memiliki bendera "Tidak Ada" atau "Tidak terdefinisi" untuk menguji tidak memiliki bendera yang ditetapkan. Ini sama sekali tidak diperlukan, tetapi ini adalah praktik yang baik. Hal penting untuk diingat tentang hal ini ditunjukkan oleh Leonid dalam jawabannya.
Andy
5
@Roylee Sebenarnya direkomendasikan oleh Microsoft untuk memberikan Nonebendera dengan nilai nol. Lihat msdn.microsoft.com/en-us/library/vstudio/…
ThatMatthew
Banyak orang juga berpendapat bahwa perbandingan bit terlalu sulit untuk dibaca sehingga harus dihindari demi koleksi bendera, di mana Anda hanya dapat melakukan pengumpulan. Bendera berisi
MikeT
Anda cukup dekat, kecuali Anda harus membalikkan Anda logika, Anda memerlukan bitwise &Operator untuk perbandingan, |seperti tambahan: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0mengevaluasi untuk false, semua yang lainnya true.
Damian Vogel

Jawaban:

321

Di .NET 4 ada metode baru Enum.HasFlag . Ini memungkinkan Anda untuk menulis:

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

yang jauh lebih mudah dibaca, IMO.

Sumber .NET menunjukkan bahwa ini melakukan logika yang sama dengan jawaban yang diterima:

public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
                "Argument_EnumTypeDoesNotMatch", 
                flag.GetType(), 
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag); 
}
Phil Devaney
sumber
23
Ah, akhirnya ada sesuatu yang keluar dari kotak. Ini hebat, saya sudah menunggu fitur yang relatif sederhana ini sejak lama. Senang mereka memutuskan untuk memasukkannya.
Rob van Groenewoud
9
Namun, perhatikan jawaban di bawah ini yang menunjukkan masalah kinerja dengan metode ini - ini mungkin masalah bagi sebagian orang. Untungnya bukan untuk saya.
Andy Mortimer
2
Pertimbangan kinerja untuk metode ini adalah tinju karena mengambil argumen sebagai turunan dari Enumkelas.
Adam Houldsworth
1
Untuk informasi tentang masalah kinerja, lihat jawaban ini: stackoverflow.com/q/7368652/200443
Maxence
180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) adalah operasi DAN bitwise.

FlagTest.Flag1setara dengan 001dengan enum OP. Sekarang katakanlah testItemmemiliki Flag1 dan Flag2 (jadi bitwise 101):

  001
 &101
 ----
  001 == FlagTest.Flag1
Scott Nichols
sumber
2
Apa sebenarnya logika di sini? Kenapa predikat itu harus ditulis seperti ini?
Ian R. O'Brien
4
@ IanR.O'Brien Flag1 | Flag 2 diterjemahkan menjadi 001 atau 010 yang sama dengan 011, sekarang jika Anda melakukan kesetaraan dari 011 == Flag1 atau diterjemahkan 011 == 001, yang selalu menghasilkan false. Sekarang jika Anda melakukan bitwise DAN dengan Flag1 maka itu diterjemahkan ke 011 DAN 001 yang mengembalikan 001 sekarang melakukan persamaan mengembalikan benar, karena 001 == 001
pqsk
Ini adalah solusi terbaik karena HasFlags jauh lebih intensif sumber daya. Dan terlebih lagi semua yang dilakukan HasFlags, ini dilakukan oleh kompiler
Sebastian Xawery Wiśniowiecki
78

Bagi mereka yang kesulitan memvisualisasikan apa yang terjadi dengan solusi yang diterima (yang ini),

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (sesuai pertanyaan) didefinisikan sebagai,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Kemudian, dalam pernyataan if, sisi kiri perbandingan adalah,

(testItem & flag1) 
 = (011 & 001) 
 = 001

Dan pernyataan if penuh (yang mengevaluasi true jika flag1diatur dalam testItem),

(testItem & flag1) == flag1
 = (001) == 001
 = true
Sekhat
sumber
25

@ phil-devaney

Perhatikan bahwa kecuali dalam kasus yang paling sederhana, Enum.HasFlag membawa penalti performa yang berat dibandingkan dengan menuliskan kode secara manual. Pertimbangkan kode berikut:

[Flags]
public enum TestFlags
{
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256,
    Ten = 512
}


class Program
{
    static void Main(string[] args)
    {
        TestFlags f = TestFlags.Five; /* or any other enum */
        bool result = false;

        Stopwatch s = Stopwatch.StartNew();
        for (int i = 0; i < 10000000; i++)
        {
            result |= f.HasFlag(TestFlags.Three);
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*

        s.Restart();
        for (int i = 0; i < 10000000; i++)
        {
            result |= (f & TestFlags.Three) != 0;
        }
        s.Stop();
        Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*        

        Console.ReadLine();
    }
}

Lebih dari 10 juta iterasi, metode ekstensi HasFlags mengambil 4793 ms kekalahan, dibandingkan dengan 27 ms untuk implementasi bitwise standar.

Chuck Dee
sumber
5
Memang. Jika Anda melihat implementasi HasFlag, Anda akan melihat bahwa ia melakukan "GetType ()" di kedua operan, yang sangat lambat. Maka itu "Enum.ToUInt64 (value.GetValue ());" pada kedua operan sebelum melakukan pemeriksaan bitwise.
user276648
1
Saya menjalankan tes Anda beberapa kali dan mendapat ~ 500ms untuk HasFlags dan ~ 32ms untuk bitwise. Sementara masih urutan besarnya lebih cepat dengan bitwise, HasFlags adalah urutan besarnya dari tes Anda. (Jalankan tes pada Core
2.53
1
@MarioVW Berjalan beberapa kali pada .NET 4, i7-3770 memberikan ~ 2400ms vs ~ 20ms pada mode AnyCPU (64-bit), dan ~ 3000ms vs ~ 20ms pada mode 32-bit. .NET 4.5 mungkin telah sedikit mengoptimalkannya. Perhatikan juga perbedaan kinerja antara 64-bit dan 32-bit, yang mungkin disebabkan oleh aritmatika 64-bit yang lebih cepat (lihat komentar pertama).
Bob
1
(«flag var» & «flag value») != 0tidak bekerja untuk saya. Kondisi selalu gagal dan kompiler saya (Unity3D's Mono 2.6.5) melaporkan "peringatan CS0162: Kode yang tidak terjangkau terdeteksi" ketika digunakan dalam sebuah if (…).
Slipp D. Thompson
1
@ wraith808: Saya menyadari kesalahan yang dibuat dengan pengujian saya bahwa Anda sudah benar dalam diri Anda— kekuatan 2 … = 1, … = 2, … = 4pada nilai enum sangat penting saat digunakan [Flags]. Saya berasumsi bahwa itu akan memulai entri 1dan maju dengan Po2s secara otomatis. Perilaku terlihat konsisten di seluruh MS .NET dan Mono tanggal Unity. Maafkan saya yang menempatkan ini pada Anda.
Slipp D. Thompson
21

Saya menyiapkan metode ekstensi untuk melakukannya: pertanyaan terkait .

Pada dasarnya:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

Maka Anda dapat melakukan:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

Kebetulan konvensi yang saya gunakan untuk enums adalah singular untuk standar, jamak untuk bendera. Dengan begitu Anda tahu dari nama enum apakah itu bisa menampung beberapa nilai.

Keith
sumber
Ini seharusnya komentar tetapi karena saya pengguna baru sepertinya saya belum bisa menambahkan komentar ... public bool statis IsSet (input Enum ini, Enum matchTo) {return (Convert.ToUInt32 (input) & Konversi .ToUInt32 (matchTo))! = 0; } Apakah ada cara agar kompatibel dengan segala jenis enum (karena di sini tidak akan berfungsi jika enum Anda bertipe UInt64 atau dapat memiliki nilai negatif)?
user276648
Ini cukup berlebihan dengan Enum.HasFlag (Enum) (tersedia dalam .net 4.0)
PPC
1
@ PPC Saya tidak akan mengatakan berlebihan - banyak orang yang mengembangkan versi kerangka kerja yang lebih lama. Anda benar, pengguna .Net 4 harus menggunakan HasFlagekstensi sebagai gantinya.
Keith
4
@Keith: Juga, ada perbedaan penting: ((FlagTest) 0x1) .HasFlag (0x0) akan mengembalikan true, yang mungkin atau mungkin bukan perilaku yang diinginkan
PPC
19

Satu saran lagi ... Jangan pernah melakukan pemeriksaan biner standar dengan flag yang nilainya "0". Cek Anda pada bendera ini akan selalu benar.

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

Jika Anda biner memeriksa parameter input terhadap FullInfo - Anda mendapatkan:

detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez akan selalu benar karena APA SAJA & 0 selalu == 0.


Sebagai gantinya, Anda cukup memeriksa bahwa nilai input adalah 0:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
Leonid
sumber
Saya baru saja memperbaiki bug 0-flag. Saya pikir ini adalah kesalahan desain dalam .NET framework (3.5) karena Anda perlu tahu yang mana dari nilai-nilai bendera 0 sebelum mengujinya.
thersch
7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}
Damian
sumber
5

Untuk operasi bit, Anda perlu menggunakan operator bitwise.

Ini harus melakukan trik:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Sunting: Memperbaiki cek saya jika - Saya kembali ke C / C ++ cara saya (terima kasih kepada Ryan Farley untuk menunjukkannya)

17 dari 26
sumber
5

Mengenai hasil edit. Anda tidak bisa mewujudkannya. Saya sarankan Anda membungkus apa yang Anda inginkan ke kelas lain (atau metode ekstensi) untuk lebih dekat dengan sintaks yang Anda butuhkan.

yaitu

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}
Martin Clarke
sumber
4

Coba ini:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Pada dasarnya, kode Anda menanyakan apakah kedua set flag sama dengan satu set flag, yang jelas salah. Kode di atas hanya akan menyisakan bit Flag1 jika diset sama sekali, lalu membandingkan hasil ini dengan Flag1.

OwenP
sumber
1

bahkan tanpa [Bendera], kamu bisa menggunakan sesuatu seperti ini

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

atau jika Anda memiliki nilai nol enum

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
Waleed AK
sumber