ใช้ bitmask ใน C #


100

สมมติว่าฉันมีสิ่งต่อไปนี้

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

เมื่อฉันมีบิตจริงจำนวนมากขึ้นในสถานการณ์ในโลกแห่งความเป็นจริงสิ่งนี้ดูเหมือนจะใช้ไม่ได้แล้วจะมีวิธีใดดีไปกว่าการใช้มาสก์เพื่อตรวจสอบว่าฉันตรงตามเงื่อนไขสำหรับกะเหรี่ยงหรือไม่

ฉันนึกได้ว่าการเลื่อนไปทางซ้ายแล้วย้อนกลับแล้วเปลี่ยนไปทางขวาแล้วกลับไปล้างบิตอื่นที่ไม่ใช่ที่ฉันสนใจ แต่มันก็ดูซับซ้อนเกินไป


7
มีเพียงแสดงความคิดเห็นเกี่ยวกับการใช้งาน หากคุณกำลังดำเนินการบิตคุณควรใช้ตัวดำเนินการจัดการบิตเท่านั้น กล่าวคือคิดว่าเป็น (8 | 2) ไม่ใช่ (8 + 2)
Jeff Mercado

1
คาเรนอยากคุยกับผู้จัดการของคุณทันที
Krythic

คำตอบ:


201

วิธีดั้งเดิมในการทำเช่นนี้คือการใช้Flagsแอตทริบิวต์บนenum:

[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 ชี้เทียบเท่าของIsSetวิธีการดังกล่าวมีอยู่แล้วใน .NET Enum.HasFlag4.0: SetและUnsetวิธีการไม่ปรากฏว่ามีเทียบเท่าแม้ว่า; ดังนั้นฉันยังคงบอกว่าคลาสนี้ได้บุญ


หมายเหตุ: การใช้ enums เป็นเพียงวิธีการทั่วไปในการแก้ไขปัญหานี้ คุณสามารถแปลโค้ดทั้งหมดข้างต้นเพื่อใช้ ints แทนและมันก็ใช้ได้เช่นกัน


14
+1 เพื่อเป็นรหัสแรกที่ใช้งานได้จริง คุณสามารถทำได้(names & Names.Susan) == Names.Susanโดยไม่ต้องใช้ไฟล์None.
Matthew Flaschen

1
@ แมทธิว: โอ้ใช่จุดที่ดี ฉันเดาว่าฉันมีนิสัยที่จะกำหนดNoneมูลค่าให้กับ enums ทั้งหมดของฉันเสมอเพราะฉันพบว่ามันสะดวกสบายในหลาย ๆ สถานการณ์
Dan Tao

33
สิ่งนี้สร้างขึ้นภายในคุณไม่จำเป็นต้องใช้วิธีการช่วยเหลือของคุณ ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@ Porges: ว้าวไม่รู้ว่าฉันพลาดไปได้ยังไง ... ขอบคุณที่ชี้ให้ดู! (ดูเหมือนว่ามันใช้ได้ในฐานะของ .NET 4.0 เพียง แต่ ... ยังมีเทียบเท่าไม่มีSetวิธีดังนั้นผมว่าวิธีการช่วยเหลือเป็นอย่างน้อยไม่ได้. โดยสิ้นเชิงไร้ค่า.)
แดนเต่า

6
โปรดทราบว่าการใช้names.HasFlag(Names.Susan)เป็น(names & Names.Susan) == Names.Susanสิ่งที่ไม่ชอบเสมอ(names & Names.Susan) != Names.Noneไป ตัวอย่างเช่นถ้าคุณจะตรวจสอบว่าnames.HasFlag(Names.none)หรือnames.HasFlag(Names.Susan|Names.Karen)
ABCade

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

บิต 'และ' จะปิดบังทุกอย่างยกเว้นบิตที่ "แสดงถึง" คาเรน ตราบใดที่แต่ละคนแสดงด้วยตำแหน่งบิตเดียวคุณสามารถตรวจสอบหลายคนได้ด้วยวิธีง่ายๆ:

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

12

ฉันได้รวมตัวอย่างไว้ที่นี่ซึ่งแสดงให้เห็นว่าคุณสามารถจัดเก็บมาสก์ในคอลัมน์ฐานข้อมูลเป็น 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;

ฉันทำสิ่งที่คล้ายกัน แต่เมื่อกำหนดหน้ากากฉันทำ Mon = Math.Power (2, 0), Tues = Math.Pow (2, 1), Wed = Math.Pow (2, 2) ฯลฯ ดังนั้นตำแหน่งบิต เป็นสิ่งที่ชัดเจนกว่าเล็กน้อยสำหรับผู้ใช้ที่ไม่คุ้นเคยกับการแปลงไบนารีเป็นทศนิยม Blindy ก็ดีเช่นกันเนื่องจากมันกลายเป็นผลลัพธ์บูลีนโดยการขยับบิตที่สวมหน้ากาก
Analog Arsonist

8

ทางที่ง่าย:

[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 */}

ดูเหมือนว่าข้อมูลทั้งหมดนี้ได้ให้ไว้ข้างต้นแล้ว หากคุณกำลังให้ข้อมูลใหม่คุณควรทำเครื่องหมายให้ชัดเจน
sonyisda1

2
ตัวอย่างง่ายๆของการใช้HasFlag()และ[Flags]ไม่ได้ระบุไว้ในคำตอบอื่น ๆ
A-Sharabiani

7

ในการรวม bitmasks คุณต้องการใช้ bitwise- หรือ . ในกรณีเล็กน้อยที่ทุกค่าที่คุณรวมมี 1 บิตอยู่เสมอ (เช่นตัวอย่างของคุณ) จะเท่ากับการเพิ่มค่าเหล่านั้น อย่างไรก็ตามหากคุณมีบิตที่ทับซ้อนกันหรือ 'นำมันมาจัดการกับเคสอย่างสง่างาม

ในการถอดรหัส bitmasks คุณและค่าของคุณด้วยมาสก์ดังนี้:

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

1
คุณไม่สามารถใช้จำนวนเต็มเป็นบูลีนใน C #
เงา

0

อีกเหตุผลที่ดีจริงๆในการใช้ bitmask เทียบกับบูลแต่ละตัวคือในฐานะนักพัฒนาเว็บเมื่อรวมเว็บไซต์หนึ่งเข้ากับอีกเว็บไซต์หนึ่งเรามักจะต้องส่งพารามิเตอร์หรือแฟล็กในสตริงการสืบค้น ตราบใดที่แฟล็กทั้งหมดของคุณเป็นไบนารีการใช้ค่าเดียวเป็นบิตมาสก์จะง่ายกว่าการส่งหลายค่าเป็นบูล ฉันรู้ว่ามีวิธีอื่นในการส่งข้อมูล (GET, POST และอื่น ๆ ) แต่พารามิเตอร์ง่ายๆบนสตริงการสืบค้นนั้นส่วนใหญ่เพียงพอแล้วสำหรับรายการที่ไม่ละเอียดอ่อน พยายามส่งค่า 128 บูลบนสตริงการสืบค้นเพื่อสื่อสารกับไซต์ภายนอก นอกจากนี้ยังช่วยเพิ่มความสามารถในการไม่เพิ่มขีด จำกัด ของสตริงการสืบค้น URL ในเบราว์เซอร์


ไม่ใช่คำตอบสำหรับคำถามของ OP - ควรเป็นความคิดเห็น
sonyisda1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.