[英]Using a bitmask in C#
假設我有以下內容
int susan = 2; //0010
int bob = 4; //0100
int karen = 8; //1000
我將10(8 + 2)作為參數傳遞給方法,我想將其解碼為蘇珊和卡倫
我知道10是1010
但是我如何做一些邏輯來查看是否檢查了特定位
if (condition_for_karen) // How to quickly check whether effective karen bit is 1
現在,我能想到的是檢查我通過的號碼是否正確
14 // 1110
12 // 1100
10 // 1010
8 // 1000
當我在我的真實場景中擁有更多的實際比特時,這似乎是不切實際的,使用掩碼來檢查我是否滿足karen條件的更好方法是什么?
我可以想到向左移動然后向后移動然后向右移動然后向后移動到除了我感興趣的那個之外的其他位置,但這似乎也過於復雜。
傳統的方法是在enum
上使用Flags
屬性:
[Flags]
public enum Names
{
None = 0,
Susan = 1,
Bob = 2,
Karen = 4
}
然后,您將檢查特定名稱,如下所示:
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;
邏輯按位組合很難記住,因此我使用FlagsHelper
類使自己的生活變得更輕松*:
// 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));
}
}
這將允許我將上面的代碼重寫為:
Names names = Names.Susan | Names.Bob;
bool susanIsIncluded = FlagsHelper.IsSet(names, Names.Susan);
bool karenIsIncluded = FlagsHelper.IsSet(names, Names.Karen);
注意我也可以通過這樣做將Karen
添加到集合中:
FlagsHelper.Set(ref names, Names.Karen);
我可以用類似的方式刪除Susan
:
FlagsHelper.Unset(ref names, Names.Susan);
*正如Porges所指出的,.NET 4.0中已存在等效的IsSet
方法: Enum.HasFlag
。 但是, Set
和Unset
方法似乎沒有等價物; 所以我仍然說這堂課有一些優點。
注意:使用枚舉只是解決此問題的傳統方法。 您可以完全翻譯所有上述代碼以使用ints,它也可以正常工作。
if ( ( param & karen ) == karen )
{
// Do stuff
}
按位'和'將屏蔽除“代表”Karen的位之外的所有內容。 只要每個人都由一個位位置代表,您就可以通過簡單的方式檢查多個人:
if ( ( param & karen ) == karen )
{
// Do Karen's stuff
}
if ( ( param & bob ) == bob )
// Do Bob's stuff
}
我在這里包含了一個示例,演示了如何將掩碼作為int存儲在數據庫列中,以及如何在以后恢復掩碼:
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;
要組合您想要按位或使用的位掩碼。 在簡單的情況下,你組合的每個值都只有1位(就像你的例子),它相當於添加它們。 但是,如果你有重疊位, 或者他們優雅地處理案例。
要使用掩碼解碼您和您的值的位掩碼,如下所示:
if(val & (1<<1)) SusanIsOn();
if(val & (1<<2)) BobIsOn();
if(val & (1<<3)) KarenIsOn();
使用位掩碼與單個bools的另一個非常好的理由是作為Web開發人員,當將一個網站集成到另一個網站時,我們經常需要在查詢字符串中發送參數或標志。 只要所有標志都是二進制,就可以使用單個值作為位掩碼,而不是將多個值作為bools發送。 我知道還有其他方法可以發送數據(GET,POST等),但查詢字符串上的一個簡單參數大部分時間都足以用於非敏感項。 嘗試在查詢字符串上發送128個bool值以與外部站點進行通信。 這也增加了在瀏覽器中不限制url查詢字符串的能力
簡單的方法:
[Flags]
public enum MyFlags {
None = 0,
Susan = 1,
Alice = 2,
Bob = 4,
Eve = 8
}
要設置標志,請使用邏輯“或”運算符|
:
MyFlags f = new MyFlags();
f = MyFlags.Alice | MyFlags.Bob;
並檢查是否包含標志使用HasFlag
:
if(f.HasFlag(MyFlags.Alice)) { /* true */}
if(f.HasFlag(MyFlags.Eve)) { /* false */}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.