Sử dụng mặt nạ bit trong C #


97

Giả sử tôi có những thứ sau

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

và tôi chuyển 10 (8 + 2) làm tham số cho một phương thức và tôi muốn giải mã điều này có nghĩa là susan và karen

Tôi biết rằng 10 là 1010

nhưng làm thế nào tôi có thể thực hiện một số logic để xem liệu một bit cụ thể có được kiểm tra như trong

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

Ngay bây giờ, tất cả những gì tôi có thể nghĩ là kiểm tra xem số tôi đã vượt qua là

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

Khi tôi có số lượng bit thực lớn hơn trong kịch bản thế giới thực của mình, điều này có vẻ không thực tế, cách tốt hơn là sử dụng mặt nạ để kiểm tra xem liệu tôi có đáp ứng điều kiện chỉ karen hay không?

Tôi có thể nghĩ đến việc chuyển sang trái rồi quay lại sau đó chuyển sang phải rồi quay lại các bit rõ ràng khác với các bit mà tôi quan tâm, nhưng điều này cũng có vẻ quá phức tạp.


7
Chỉ cần phải nhận xét về việc sử dụng. Nếu bạn đang thực hiện các phép toán bit, bạn chỉ nên sử dụng các toán tử thao tác bit. tức là, hãy nghĩ về nó là (8 | 2), không phải (8 + 2).
Jeff Mercado

Karen cũng muốn nói chuyện với quản lý của bạn, ngay lập tức.
Krythic

Câu trả lời:


198

Cách truyền thống để làm điều này là sử dụng Flagsthuộc tính trên enum:

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

Sau đó, bạn kiểm tra một tên cụ thể như sau:

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;

Các kết hợp theo từng bit logic có thể khó nhớ, vì vậy tôi tự làm cho cuộc sống trở nên dễ dàng hơn với một FlagsHelperlớp học *:

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

Điều này sẽ cho phép tôi viết lại đoạn mã trên dưới dạng:

Names names = Names.Susan | Names.Bob;

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

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

Lưu ý rằng tôi cũng có thể thêm Karenvào tập hợp bằng cách thực hiện điều này:

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

Và tôi có thể xóa Susantheo cách tương tự:

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

* Như Porges chỉ ra, tương đương với các IsSetphương pháp trên đã tồn tại trong .NET 4.0: Enum.HasFlag. Tuy nhiên Set, Unsetcác phương thức và phương thức dường như không tương đương nhau; vì vậy tôi vẫn muốn nói rằng lớp này có một số điểm đáng khen.


Lưu ý: Sử dụng enums chỉ là cách thông thường để giải quyết vấn đề này. Bạn hoàn toàn có thể dịch tất cả mã trên để sử dụng int thay thế và nó sẽ hoạt động tốt.


14
+1 vì là mã đầu tiên thực sự hoạt động. Bạn cũng có thể làm (names & Names.Susan) == Names.Susan, điều này không yêu cầu a None.
Matthew Flaschen,

1
@Matthew: Ồ đúng vậy, điểm tốt. Tôi đoán rằng tôi chỉ có thói quen luôn xác định một Nonegiá trị cho tất cả các enums của mình, vì tôi thấy nó kết thúc thuận tiện trong nhiều trường hợp.
Dan Tao

30
Điều này được tích hợp sẵn, bạn không cần phương thức trợ giúp của mình ...var susanIsIncluded = names.HasFlag(Names.Susan);
porges

2
@Porges: Chà, không hiểu sao tôi lại bỏ lỡ điều đó ... cảm ơn vì đã chỉ ra điều đó! (Hình như nó chỉ có sẵn như của .NET 4.0, mặc dù ... cũng có, không có tương đương cho các Setphương pháp Vì vậy, tôi muốn nói những phương pháp helper ít nhất không. Hoàn toàn vô giá trị.)
Dan Tao

6
Lưu ý, rằng việc sử dụng names.HasFlag(Names.Susan)cũng giống như (names & Names.Susan) == Names.Susanđó không phải lúc nào cũng giống như (names & Names.Susan) != Names.None. Ví dụ: nếu bạn sẽ kiểm tra xem names.HasFlag(Names.none)hoặcnames.HasFlag(Names.Susan|Names.Karen)
ABCade

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

Bitwise 'và' sẽ che khuất mọi thứ ngoại trừ bit "đại diện" cho Karen. Miễn là mỗi người được đại diện bởi một vị trí bit, bạn có thể kiểm tra nhiều người bằng cách đơn giản:

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

12

Tôi đã bao gồm một ví dụ ở đây minh họa cách bạn có thể lưu trữ mặt nạ trong cột cơ sở dữ liệu dưới dạng số nguyên và cách bạn sẽ khôi phục mặt nạ sau này:

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;

Tôi đã làm điều gì đó tương tự nhưng khi xác định mặt nạ, tôi đã thực hiện Mon = Math.Power (2, 0), Third = Math.Pow (2, 1), Wed = Math.Pow (2, 2), v.v. vì vậy vị trí bit rõ ràng hơn một chút đối với những người sử dụng không quen chuyển đổi từ nhị phân sang thập phân. Blindy's cũng tốt vì nó biến thành kết quả boolean bằng cách dịch chuyển bit bị che.
Analog Arsonist

7

Để kết hợp các mặt nạ bit, bạn muốn sử dụng bitwise- hoặc . Trong trường hợp nhỏ mà mỗi giá trị bạn kết hợp có chính xác 1 bit trên (như ví dụ của bạn), điều đó tương đương với việc thêm chúng. Tuy nhiên, nếu bạn có các bit chồng chéo hoặc 'nhập chúng sẽ xử lý trường hợp một cách duyên dáng.

Để giải mã các mặt nạ bit, bạn giá trị của bạn bằng một mặt nạ, như sau:

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

1
Bạn không thể sử dụng một số nguyên làm boolean trong C #.
Bóng

6

Cách dễ dàng:

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

Để đặt cờ, hãy sử dụng toán tử logic "hoặc" |:

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

Và để kiểm tra xem cờ có được bao gồm hay không, hãy sử dụng HasFlag:

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

Có vẻ như tất cả thông tin này đã được cung cấp ở trên. Nếu bạn đưa ra bất kỳ thông tin mới nào, bạn nên đánh dấu nó một cách rõ ràng.
sonyisda1

1
ví dụ đơn giản về việc sử dụng HasFlag()[Flags]không được cung cấp trong các câu trả lời khác.
A-Sharabiani

0

Một lý do thực sự tốt khác để sử dụng bitmask so với bools cá nhân là với tư cách là nhà phát triển web, khi tích hợp trang web này với trang web khác, chúng tôi thường xuyên cần gửi các tham số hoặc cờ trong chuỗi truy vấn. Miễn là tất cả các cờ của bạn là nhị phân, thì việc sử dụng một giá trị duy nhất làm mặt nạ bit đơn giản hơn nhiều so với việc gửi nhiều giá trị dưới dạng bools. Tôi biết có những cách khác để gửi dữ liệu (GET, POST, v.v.), nhưng một tham số đơn giản trên chuỗi truy vấn hầu hết là đủ cho các mục không nhạy cảm. Cố gắng gửi 128 giá trị bool trên một chuỗi truy vấn để giao tiếp với một trang web bên ngoài. Điều này cũng cung cấp thêm khả năng không đẩy giới hạn trên các chuỗi truy vấn url trong trình duyệt


Không thực sự là một câu trả lời cho câu hỏi của OP - lẽ ra phải là một bình luận.
sonyisda1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.