C # Bayrakları Karşılaştırma?


155

Aşağıda bir bayrak numaralandırma var.

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

İf ifadesinin doğru olarak değerlendirilmesini sağlayamıyorum.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

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

Bunu nasıl doğrulayabilirim?


Yanlışsam beni düzelt, bayrak değeri olarak kullanılmaya uygun mu?
Roy Lee

4
@Roylee: 0 kabul edilebilir ve hiçbir bayrak ayarlanmadığını test etmek için "Yok" veya "Tanımsız" bayrağına sahip olmak iyi bir fikirdir. Hiçbir şekilde gerekli değildir, ancak iyi bir uygulamadır. Bu konuda hatırlanması gereken önemli şey, cevabında Leonid tarafından belirtiliyor.
Andy

5
@Roylee Aslında Microsoft tarafından Nonesıfır değerine sahip bir bayrak sağlaması önerilir . Bkz msdn.microsoft.com/en-us/library/vstudio/...
ThatMatthew

Bir çok insan da bit karşılaştırmasının okunması çok zor olduğunu iddia ediyor, bu yüzden sadece koleksiyon yapabileceğiniz bir bayrak koleksiyonu lehine kaçınılması gerekiyor. Bayrak
içeriyor

Sen sana mantığını ters zorunda haricinde sen bitsel ihtiyaç oldukça yakın &karşılaştırma için operatörü, |bir ek gibidir: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. diğer her şeyi 0değerlendirir . falsetrue
Damian Vogel

Yanıtlar:


321

.NET 4'te yeni bir yöntem Enum.HasFlag var . Bu şunları yazmanıza izin verir:

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

çok daha okunabilir olan IMO.

.NET kaynağı, bunun kabul edilen yanıtla aynı mantığı gerçekleştirdiğini gösterir:

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); 
}

23
Ah, sonunda kutunun dışında bir şey. Bu harika, bu nispeten basit özelliği uzun zamandır bekliyordum. Sevindim onlar kaymaya karar verdi.
Rob van Groenewoud

9
Ancak, bu yöntemin performans sorunlarını gösteren aşağıdaki cevaba dikkat edin - bu bazı insanlar için bir sorun olabilir. Mutlu benim için değil.
Andy Mortimer

2
Bu yöntemin performans değerlendirmesi, Enumsınıfın bir örneği olarak bağımsız değişkenleri aldığından bokstur .
Adam Houldsworth

1
Performans sorunuyla ilgili bilgi için şu cevaba bakın: stackoverflow.com/q/7368652/200443
Maxence

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

(testItem & FlagTest.Flag1) bitsel bir AND işlemidir.

FlagTest.Flag1001OP enum ile eşdeğerdir . Diyelim ki testItemFlag1 ve Flag2 var (bu yüzden bitsel 101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
Burada mantık tam olarak nedir? Neden yüklem böyle yazılmalıdır?
Ian R. O'Brien

4
@ IanR.O'Brien Bayrağı1 | Bayrak 2, 011 ile aynı olan 001 veya 010 anlamına gelir; şimdi 011 == Flag1 veya çevrilmiş 011 == 001 eşitliğini yaparsanız, her zaman yanlış döndürür. Şimdi bitsel VE AND1 ile yaparsanız, o zaman 001 döndürür 011 ve 001 çevirir, şimdi eşitlik yapıyor 001 == 001
pqsk 6:15

Bu en iyi çözüm çünkü HasFlags çok daha kaynak yoğun. Üstelik HasFlags'ın yaptığı her şey, burada derleyici tarafından yapılır
Sebastian Xawery Wiśniowiecki

78

Kabul edilen çözümle olanları görselleştirmekte sorun yaşayanlar için (budur),

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

testItem (soruya göre),

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Sonra, if ifadesinde karşılaştırmanın sol tarafı,

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

Ve tam if ifadesi ( flag1ayarlanmışsa true olarak değerlendirilir testItem),

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ Phil-Devaney

En basit durumlar dışında, Enum.HasFlag kodun manuel olarak yazılmasına kıyasla ağır bir performans cezası taşıdığını unutmayın . Aşağıdaki kodu göz önünde bulundurun:

[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();
    }
}

10 milyondan fazla yineleme, HasFlags genişletme yöntemi, standart bitsel uygulama için 27 ms'ye kıyasla 4793 ms'lik bir kuyruk alır.


5
Aslında. HasFlag uygulamasına bakarsanız, her iki işlenende de oldukça yavaş olan bir "GetType ()" yaptığını görürsünüz. Sonra "Enum.ToUInt64 (value.GetValue ());" her iki işlenende de bitsel kontrol yapmadan önce.
user276648 26:11

1
Testinizi birkaç kez yaptım ve HasFlags için ~ 500ms ve bitsel için ~ 32ms aldım. Halen bitsel olarak daha büyük bir mertebeden daha hızlı olmakla birlikte, HasFlags testinizden daha düşük bir mertebeydi. (Testi
2.5GHz

1
@MarioVW .NET 4'te birkaç kez çalışan i7-3770, AnyCPU (64 bit) modunda ~ 2400ms ~ ~ 20ms ve 32-bit modunda ~ 3000ms ~ ~ 20ms verir. .NET 4.5 bunu biraz optimize etmiş olabilir. Ayrıca, 64-bit ve 32-bit derlemeler arasındaki performans farkına dikkat edin; bu, 64-bit aritmetiğin daha hızlı olmasından kaynaklanabilir (ilk yoruma bakın).
Bob

1
(«flag var» & «flag value») != 0benim için çalışmıyor. Durum her zaman başarısız olur ve benim derleyici (Unity3D en Mono 2.6.5) bir rapor “uyarısı CS0162: ulaşılamaz kod algılandı” bir kullanıldığında if (…).
Slipp D.Thomp

1
@ wraith808: Hatanın benimkiyle doğru olduğunu test etmemle ilgili olduğunu fark ettim - … = 1, … = 2, … = 4enum değerlerindeki 2'nin gücü kullanırken kritik derecede önemlidir [Flags]. Ben en girişleri başlayacak varsayarak edildi 1otomatik Po2s tarafından ve avans. Davranış MS .NET ve Unity'nin tarihli Mono'sunda tutarlı görünüyor. Özür dilerim bunu sana veriyor.
Slipp D. Thompson

21

Bunu yapmak için bir uzatma yöntemi ayarladım: ilgili soru .

Temelde:

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

Sonra şunları yapabilirsiniz:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

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

Bu arada, numaralandırmalar için kullandığım sözleşme standart için tekil, bayraklar için çoğuldur. Bu şekilde numaralandırma adından birden çok değer tutabildiğini bilirsiniz.


Bu bir yorum olmalı, ancak yeni bir kullanıcı olduğum için henüz yorum ekleyemiyorum ... genel statik bool IsSet (bu Enum girişi, Enum matchTo) {return (Convert.ToUInt32 (input) & Convert .ToUInt32 (matchTo))! = 0; } Herhangi bir tür sayım ile uyumlu olmanın bir yolu var mı (çünkü burada sayımınız UInt64 türündeyse veya negatif değerler içeriyorsa işe yaramaz)?
user276648

Bu, Enum.HasFlag (Enum) (.net 4.0 sürümünde) ile oldukça gereksizdir
PPC

1
@PPC Tam olarak gereksiz diyemem - çerçevenin eski sürümlerinde birçok insan gelişiyor. Doğru .Net 4 kullanıcıları HasFlagbunun yerine uzantıyı kullanmalıdır.
Keith

4
@Keith: Ayrıca, dikkate değer bir fark var: ((FlagTest) 0x1) .HasFlag (0x0) doğru dönecek, bu istenen bir davranış olabilir veya olmayabilir
PPC

19

Bir tavsiye daha ... Standart ikili kontrolü asla değeri "0" olan bayrakla yapmayın. Bu bayrak üzerindeki çekiniz her zaman doğru olacaktır.

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

Girdi parametresini FullInfo ile ikili olarak kontrol ederseniz - şunları elde edersiniz:

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

bPRez her zaman HERHANGİ BİR & 0 = = 0 olarak doğru olacaktır.


Bunun yerine, giriş değerinin 0 olup olmadığını kontrol etmelisiniz:

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

Böyle bir 0-bayrak hatasını düzelttim. Ben test etmeden önce hangi bayrak değerleri 0 olduğunu bilmek gerekir çünkü bu .NET framework (3.5) bir tasarım hatası olduğunu düşünüyorum.
thersch

7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}

5

Bit işlemleri için bitsel işleçleri kullanmanız gerekir.

Bu hile yapmalı:

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

Düzenleme: Benim kontrol düzeltildi - Ben C / C ++ yollarına geri kaydı (işaret için Ryan Farley sayesinde)


5

Düzenleme ile ilgili. Bunu doğru yapamazsın. İhtiyacınız olan sözdizimine yaklaşmak için ne istediğinizi başka bir sınıfa (veya uzantı yöntemine) sarmanızı öneririm.

yani

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

4

Bunu dene:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Temel olarak, kodunuz her iki bayrağa sahip olmanın bir bayrağa sahip olmakla aynı olup olmadığını soruyor. Yukarıdaki kod, hiç ayarlanmamışsa yalnızca Flag1 biti ayarlanmış olarak bırakacak, ardından bu sonucu Flag1 ile karşılaştırılacaktır.


1

[Bayraklar] olmadan bile, böyle bir şey kullanabilirsiniz

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

veya Sıfır değeri enum'unuz varsa

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.