C # 'da bir bit maskesi kullanma


100

Aşağıdakilere sahip olduğumu varsayalım

int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000

ve bir yönteme parametre olarak 10 (8 + 2) geçiriyorum ve bunu susan ve karen anlamına gelecek şekilde çözmek istiyorum

10'un 1010 olduğunu biliyorum

ancak belirli bir bitin olduğu gibi kontrol edilip edilmediğini görmek için nasıl biraz mantık yapabilirim

if (condition_for_karen) // How to quickly check whether effective karen bit is 1

Şu anda tek düşünebildiğim, geçtiğim numaranın olup olmadığını kontrol etmek

14 // 1110
12 // 1100
10 // 1010
8 //  1000

Gerçek dünya senaryomda daha fazla sayıda gerçek bit olduğunda, bu pratik görünmüyor, sadece Karen için koşulu karşılayıp karşılamadığımı kontrol etmek için bir maske kullanmanın daha iyi bir yolu nedir?

İlgilendiğimden farklı olan bitleri netleştirmek için sola, sonra geri, sonra sağa, sonra geri kaydırmayı düşünebilirim, ancak bu aynı zamanda aşırı karmaşık görünüyor.


7
Sadece kullanım hakkında yorum yapmak zorunda kaldı. Bit işlemleri yapıyorsanız, yalnızca bit işleme operatörlerini kullanmalısınız. yani, (8 | 2) olarak düşünün, (8 + 2) değil.
Jeff Mercado

1
Karen ayrıca derhal müdürünüzle konuşmak istiyor.
Krythic

Yanıtlar:


201

Bunu yapmanın geleneksel yolu, Flagsözniteliği bir enum:

[Flags]
public enum Names
{
    None = 0,
    Susan = 1,
    Bob = 2,
    Karen = 4
}

Ardından belirli bir adı aşağıdaki gibi kontrol edersiniz:

Names names = Names.Susan | Names.Bob;

// evaluates to true
bool susanIsIncluded = (names & Names.Susan) != Names.None;

// evaluates to false
bool karenIsIncluded = (names & Names.Karen) != Names.None;

Mantıksal bitsel kombinasyonları hatırlamak zor olabilir, bu yüzden bir FlagsHelpersınıfla hayatı kendim için kolaylaştırıyorum *:

// The casts to object in the below code are an unfortunate necessity due to
// C#'s restriction against a where T : Enum constraint. (There are ways around
// this, but they're outside the scope of this simple illustration.)
public static class FlagsHelper
{
    public static bool IsSet<T>(T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        return (flagsValue & flagValue) != 0;
    }

    public static void Set<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue | flagValue);
    }

    public static void Unset<T>(ref T flags, T flag) where T : struct
    {
        int flagsValue = (int)(object)flags;
        int flagValue = (int)(object)flag;

        flags = (T)(object)(flagsValue & (~flagValue));
    }
}

Bu, yukarıdaki kodu şu şekilde yeniden yazmama izin verir:

Names names = Names.Susan | Names.Bob;

bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);

bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);

Not Bunu Karenyaparak sete de ekleyebilirim :

FlagsHelper.Set(ref names, Names.Karen);

Ve Susanbenzer şekilde kaldırabilirim :

FlagsHelper.Unset(ref names, Names.Susan);

* As Porges, bir eşdeğer işaret IsSetzaten .NET 4.0 bulunmaktadır yukarıdaki yöntemle: Enum.HasFlag. Yine de Setve Unsetyöntemlerinin eşdeğerleri yok gibi görünüyor; bu yüzden yine de bu sınıfın bazı haklarının olduğunu söyleyebilirim.


Not: Numaralandırmaları kullanmak, bu sorunu çözmenin geleneksel yoludur. Bunun yerine tüm kodları kullanmak için yukarıdaki kodu tamamen çevirebilirsiniz ve aynı şekilde çalışacaktır.


14
Gerçekten çalışan ilk kod olduğu için +1. Ayrıca (names & Names.Susan) == Names.Susan, bir None.
Matthew Flaschen

1
@Matthew: Oh evet, iyi nokta. Sanırım Nonetüm numaralandırmalarım için her zaman bir değer tanımlama alışkanlığı edindim, çünkü bunu birçok senaryoda uygun buluyorum.
Dan Tao

33
Bu yerleşiktir, yardımcı yöntemlere ihtiyacınız yoktur ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges: Vay canına, bunu nasıl kaçırdığımı bilmiyorum ... Bunu belirttiğin için teşekkürler! (Ayrıca, için eşdeğer var gerçi ... bunun gibi görünüyor, .NET 4.0 itibariyle sadece ulaşılabilir Setyöntemle Yani, yardımcı yöntemler en azından değildir söyleyebilirim. Tamamen değersiz.)
Dan Tao

6
Not kullanarak o names.HasFlag(Names.Susan)gibidir (names & Names.Susan) == Names.Susanki her zaman böyle değildir (names & Names.Susan) != Names.None. Örneğin eğer kontrol edeceğiz eğer names.HasFlag(Names.none)yanames.HasFlag(Names.Susan|Names.Karen)
ABCade

20
if ( ( param & karen ) == karen )
{
  // Do stuff
}

Bitsel 've', Karen'ı "temsil eden" bit dışında her şeyi maskeleyecektir. Her kişi tek bir bit konumu ile temsil edildiği sürece, birden fazla kişiyi basit bir şekilde kontrol edebilirsiniz:

if ( ( param & karen ) == karen )
{
  // Do Karen's stuff
}
if ( ( param & bob ) == bob )
  // Do Bob's stuff
}

12

Buraya maskeyi bir veritabanı sütununda int olarak nasıl saklayabileceğinizi ve daha sonra maskeyi nasıl eski haline getireceğinizi gösteren bir örnek ekledim:

public enum DaysBitMask { Mon=0, Tues=1, Wed=2, Thu = 4, Fri = 8, Sat = 16, Sun = 32 }


DaysBitMask mask = DaysBitMask.Sat | DaysBitMask.Thu;
bool test;
if ((mask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((mask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((mask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

// Store the value
int storedVal = (int)mask;

// Reinstate the mask and re-test
DaysBitMask reHydratedMask = (DaysBitMask)storedVal;

if ((reHydratedMask & DaysBitMask.Sat) == DaysBitMask.Sat)
    test = true;
if ((reHydratedMask & DaysBitMask.Thu) == DaysBitMask.Thu)
    test = true;
if ((reHydratedMask & DaysBitMask.Wed) != DaysBitMask.Wed)
    test = true;

Benzer bir şey yaptım ama maskeyi tanımlarken Mon = Math.Power (2, 0), Tues = Math.Pow (2, 1), Wed = Math.Pow (2, 2), vb. ikiliden ondalık dönüşüme alışkın olmayan kullanıcılar için biraz daha belirgindir. Blindy's de iyidir çünkü maskelenmiş biti kaydırarak bir boole sonucuna dönüşür.
Analog Arsonist

8

Kolay yol:

[Flags]
public enum MyFlags {
    None = 0,
    Susan = 1,
    Alice = 2,
    Bob = 4,
    Eve = 8
}

İşaretleri ayarlamak için mantıksal "veya" operatörünü kullanın |:

MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;

Ve bir bayrağın dahil olup olmadığını kontrol etmek için şunu kullanın HasFlag:

if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}

Görünüşe göre tüm bu bilgiler yukarıda zaten sağlanmış. Herhangi bir yeni bilgi veriyorsanız, onu açıkça işaretlemelisiniz.
sonyisda1

2
kullanmanın basit bir örnek HasFlag()ve [Flags]diğer yanıtlar sağlanmadı.
A-Sharabiani

7

Bit maskelerini kullanmak istediğiniz bit maskelerini birleştirmek için veya . Birleştirdiğiniz her değerin tam olarak 1 bit olduğu önemsiz durumda (örneğiniz gibi), onları eklemeye eşdeğerdir. Bununla birlikte, çakışan bitleriniz varsa veya bunları yapmak durumu incelikle ele alır.

Bit maskelerini siz ve değerinizi bir maske ile çözmek için , örneğin:

if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();

1
C # 'da mantıksal değer olarak bir tamsayıyı kullanamazsınız.
Gölge

0

Bir web geliştiricisi olarak bir web sitesini diğerine entegre ederken, sorgu dizesinde sık sık parametreler veya işaretler göndermemiz gerekir. Tüm bayraklarınız ikili olduğu sürece, tek bir değeri bit maskesi olarak kullanmayı birden çok değeri bool olarak göndermekten çok daha basit hale getirir. Veri göndermenin başka yolları (GET, POST, vb.) Olduğunu biliyorum, ancak sorgu dizesindeki basit bir parametre çoğu zaman hassas olmayan öğeler için yeterlidir. Harici bir siteyle iletişim kurmak için sorgu dizesinde 128 bool değeri göndermeyi deneyin. Bu ayrıca tarayıcılarda url sorgu dizeleri sınırını zorlamama özelliği de sağlar.


OP'nin sorusuna gerçekten bir cevap değil - bir yorum olmalıydı.
sonyisda1
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.