Numaralandırmalarda en yaygın C # bitsel işlemleri


201

Hayatım boyunca, bir bit alanında biraz ayarlamayı, silmeyi, değiştirmeyi veya test etmeyi hatırlayamıyorum. Emin değilim ya da onları karıştırıyorum çünkü bunlara nadiren ihtiyacım var. Yani bir "bit hile sayfası" olması güzel olurdu.

Örneğin:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

veya

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

Diğer tüm işlemlerin, tercihen bir [Flags] enum kullanarak C # sözdiziminde örnekler verebilir misiniz?


5
Bu daha önce burada
Greg Rogers

7
bu konuyla ilgili soru ipuçlarında bu bağlantı görünmüyor.
cori

10
Bu soru c / c ++ için etiketlenmiştir, bu nedenle sözdizimi aynı görünse bile C # hakkında bilgi arayan biri muhtemelen oraya bakmaz.
Adam Lassek

Bit testini yapmanın daha az ayrıntılı bir yolunun farkında değilim
Andy Johnson

2
@Andy, şimdi .NET 4'te bit testi için bir API var.
Drew Noakes

Yanıtlar:


288

Bu uzantılar üzerinde biraz daha çalıştım - Kodu burada bulabilirsiniz

Sık kullandığım System.Enum'u genişleten bazı uzantı yöntemleri yazdım ... Kurşun geçirmez olduklarını iddia etmiyorum, ancak yardımcı oldular ... Yorumlar kaldırıldı ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

Sonra aşağıdaki gibi kullanılır

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
Ben de bunu yararlı buldum - Herhangi bir fikir nasıl altta yatan herhangi bir tür üzerinde çalışır, böylece nasıl değiştirebilirim?
Charlie Salts

7
Bu uzantılar sadece günümü, haftamı, ayımı ve büyük olasılıkla yılımı oluşturdu.
thaBadDawg

Teşekkür ederim! Herkes: Hugoware'nin bağlandığı güncellemeyi kontrol ettiğinizden emin olun.
Helge Klein

Çok güzel bir uzantı kümesi. Boks kullanmayan bir alternatif düşünemiyorum ve bu kısa ve özlü. Yeni HasFlagyöntem bile Enumboks gerektirir.
Drew Noakes

4
@Drew: Bokstan kaçınmanın bir yolu için code.google.com/p/unconstrained-melody adresine bakın :)
Jon Skeet

109

.NET 4'te artık şunları yazabilirsiniz:

flags.HasFlag(FlagsEnum.Bit4)

4
FlagsEnumÇirkin bir isim olsa da , işaret etmek için +1 . :)
Jim Schubert

2
@Jim, belki. Orijinal soruda kullanılan örnek bir addır, bu nedenle kodunuzda değiştirebilirsiniz.
Drew Noakes

14
Biliyorum! Ama çirkin isimler IE6'ya benziyor ve muhtemelen asla gitmeyecek :(
Jim Schubert

5
@JimSchubert, yine, sorunu karıştırmamak için orijinal sorudan tür adını tekrar ürettim. .NET Sayım Tipi Adlandırma Kuralları tüm belirtmek [Flags]adı böylece çeteleler, pluralised ada sahip olmamalıdır FlagsEnumçirkinlik bile daha ciddi sorunları vardır.
Drew Noakes

1
Çerçeve Tasarım Yönergeleri'ni de öneririm : Yeniden Kullanılabilir .NET Kütüphaneleri için Kurallar, Deyimler ve Desenler . Satın almak biraz pahalı, ama Safari Online ve Books24x7'nin her ikisi için de aboneler için teklif ettiğine inanıyorum.
Jim Schubert

89

Deyim bitleri ayarlamak için bitsel veya eşit operatörün kullanılmasıdır:

flags |= 0x04;

Biraz temizlemek için deyim bitsel ve olumsuz olarak kullanmaktır:

flags &= ~0x04;

Bazen bitinizi tanımlayan bir ofsetiniz olur ve sonra deyim bunları sol kaydırma ile birlikte kullanmaktır:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@Çizdi

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.


10
Kesinlikle ilginç ve işaret etmek iyi. Kullanımı dikkate almanız gerekir. Buna göre, birkaç yüz bin veya daha fazla operasyon yapmıyorsanız, muhtemelen bunu fark etmeyeceksiniz bile.
Joshua Hayes

7
HasFlagYöntem bu farkın hangi hesapların, boks / unboxing kapsar. Ancak maliyet o kadar önemsiz (0.4µs) ki, sıkı bir döngüde olmadıkça, her gün daha okunabilir (ve daha az olası buggy) bildirici API çağrısını alırdım.
Drew Noakes

8
Kullanıma bağlı olarak, bir sorun olabilir. Yükleyicilerle biraz çalıştığımdan, işaret etmenin iyi olduğunu düşündüm.
Chuck Dee

11

.NET'in yerleşik bayrak numaralandırma işlemleri maalesef oldukça sınırlıdır. Çoğu zaman kullanıcılar bitsel işlem mantığını bulmakla kalırlar.

.NET 4'te, kullanıcı kodunu basitleştirmeye yardımcı olan yöntem HasFlageklendi, Enumancak maalesef bununla ilgili birçok sorun var.

  1. HasFlag yalnızca belirtilen numara türünü değil, her tür numara değeri bağımsız değişkenini kabul ettiğinden tür güvenli değildir.
  2. HasFlagdeğerin, enum değeri bağımsız değişkeni tarafından sağlanan tüm bayraklara sahip olup olmadığını kontrol edip etmediği belirsizdir. Bu arada.
  3. HasFlag ayırmaya ve dolayısıyla daha fazla çöp koleksiyonuna neden olan boks gerektirdiğinden oldukça yavaştır.

Kısmen .NET'in bayrak numaraları için sınırlı desteği nedeniyle , bu sorunların her birini ele alan ve bayrak numaraları ile uğraşmayı daha kolay hale getiren OSS kütüphanesi Enums.NET'i yazdım .

Aşağıda, .NET çerçevesini kullanarak eşdeğer uygulamaları ile birlikte sağladığı işlemlerden bazıları verilmiştir.

Bayrakları Birleştir

.AĞ             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Bayrakları Kaldır

.AĞ             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Ortak Bayraklar

.AĞ             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Bayrakları Aç / Kapat

.AĞ             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Tüm Bayraklara Sahip

.NET             (flags & otherFlags) == otherFlags veyaflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


Bayrakları Var

.AĞ             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Bayrakları Alın

.AĞ

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


Bu geliştirmeleri .NET Core ve belki de sonunda tam .NET Framework içine dahil etmeye çalışıyorum. Sen benim önerisini kontrol edebilirsiniz burada .


7

C ++ sözdizimi, bit 0'ın LSB olduğunu varsayarsak, bayrakların uzun süre imzasız olduğunu varsayarsak:

Ayarlanıp ayarlanmadığını kontrol edin:

flags & (1UL << (bit to test# - 1))

Ayarlanmamış olup olmadığını kontrol edin:

invert test !(flag & (...))

Ayarlamak:

flag |= (1UL << (bit to set# - 1))

Açık:

flag &= ~(1UL << (bit to clear# - 1))

geçiş:

flag ^= (1UL << (bit to set# - 1))

3

En iyi performans ve sıfır çöp için bunu kullanın:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

Biraz test etmek için aşağıdakileri yaparsınız: (bayrakların 32 bit sayı olduğu varsayılarak)

Test Bitleri:

if((flags & 0x08) == 0x08)
(Eğer bit 4 ayarlanmışsa doğru olur) Geri Dön (1 - 0 veya 0 - 1):
flags = flags ^ 0x08;
Bit 4'ü Sıfırla:
flags = flags & 0xFFFFFF7F;


2
-1 çünkü bu numaralandırmalarla bile uğraşmıyor mu? Artı, değerleri elle kodlamak kırılgandır ... En azından yazmak ~0x08yerine 0xFFFFFFF7yazardım ... (0x8 için gerçek maske)
Ben Mosher

1
İlk başta Ben'in -1'inin sert olduğunu düşünüyordum, ancak "0xFFFFFF7F" kullanımı bunu özellikle kötü bir örnek yapıyor.
ToolmakerSteve

2

Bu ilham, Delphi'deki Setler dizini olarak kullanılarak şu durumlarda esinlenmiştir:

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
BindingFlags byte enum ise bu bile derlenmez: this.flags & = ~ index;
amuliar

0

C ++ işlemleri şunlardır: & | ^ ~ (ve, veya, xor ve bitsel olmayan işlemler için). Ayrıca, bitshift işlemleri olan >> ve << da ilgi çekicidir.

Bu nedenle, bir bayrağa ayarlanmış bir biti test etmek için şunu kullanırsınız: if (flags & 8) // test bit 4 ayarlanmışsa


8
Soru c ++ ile değil, c ++ ile ilgilidir
Andy Johnson

3
Öte yandan, C # aynı operatörleri kullanır: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

3
@ Workmad3 savunmasında, orijinal etiketlerde C ve C ++ vardı
pqsk
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.