Làm thế nào để so sánh cờ trong C #?


155

Tôi có một lá cờ enum dưới đây.

[Flags]
public enum FlagTest
{
    None = 0x0,
    Flag1 = 0x1,
    Flag2 = 0x2,
    Flag3 = 0x4
}

Tôi không thể làm cho câu lệnh if đánh giá thành đúng.

FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;

if (testItem == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Làm thế nào tôi có thể làm điều này đúng?


Chỉnh sửa cho tôi nếu tôi sai, 0 có thích hợp để được sử dụng làm giá trị cờ không?
Roy Lee

4
@Roylee: 0 là chấp nhận được và nên có cờ "Không" hoặc "Không xác định" để kiểm tra không có cờ nào được đặt. Đó không phải là yêu cầu, nhưng đó là một thực hành tốt. Điều quan trọng cần nhớ về điều này được Leonid chỉ ra trong câu trả lời của ông.
Andy

5
@Roylee Thực sự được Microsoft khuyến nghị cung cấp một Nonecờ có giá trị bằng 0. Xem msdn.microsoft.com/en-us/l
Library / vudio / từ

Nhiều người cũng cho rằng so sánh bit quá khó đọc vì vậy nên tránh sử dụng bộ sưu tập cờ, trong đó bạn chỉ có thể thực hiện bộ sưu tập
cờ.contains

Bạn khá gần, ngoại trừ bạn phải đảo ngược bạn logic, bạn cần Bitwise &điều hành để so sánh, |giống như một sự bổ sung: 1|2=3, 5|2=7, 3&2=2, 7&2=2, 8&2=0. 0đánh giá false, mọi thứ khác để true.
Damian Vogel

Câu trả lời:


321

Trong .NET 4 có một phương thức mới Enum.HasFlag . Điều này cho phép bạn viết:

if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
    // Do Stuff
}

IMO dễ đọc hơn nhiều.

Nguồn .NET chỉ ra rằng điều này thực hiện logic tương tự như câu trả lời được chấp nhận:

public Boolean HasFlag(Enum flag) {
    if (!this.GetType().IsEquivalentTo(flag.GetType())) {
        throw new ArgumentException(
            Environment.GetResourceString(
                "Argument_EnumTypeDoesNotMatch", 
                flag.GetType(), 
                this.GetType()));
    }

    ulong uFlag = ToUInt64(flag.GetValue()); 
    ulong uThis = ToUInt64(GetValue());
    // test predicate
    return ((uThis & uFlag) == uFlag); 
}

23
Ah, cuối cùng là một cái gì đó ra khỏi hộp. Điều này thật tuyệt, tôi đã chờ đợi tính năng tương đối đơn giản này trong một thời gian dài. Vui mừng vì họ đã quyết định đưa nó vào.
Rob van Groenewoud

9
Tuy nhiên, lưu ý, câu trả lời dưới đây cho thấy các vấn đề về hiệu suất với phương pháp này - đây có thể là vấn đề đối với một số người. Hạnh phúc không cho tôi.
Andy Mortimer

2
Việc xem xét hiệu năng cho phương pháp này là quyền anh vì nó lấy các đối số làm ví dụ của Enumlớp.
Adam Houldsworth

1
Để biết thông tin về vấn đề hiệu suất, hãy xem câu trả lời này: stackoverflow.com/q/7368652/200443
Maxence

180
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
     // Do something
}

(testItem & FlagTest.Flag1) là một bitwise VÀ hoạt động.

FlagTest.Flag1tương đương 001với enum của OP. Bây giờ, hãy nói testItemcó Flag1 và Flag2 (vì vậy, bitwise 101):

  001
 &101
 ----
  001 == FlagTest.Flag1

2
Chính xác thì logic ở đây là gì? Tại sao vị ngữ phải được viết như thế này?
Ian R. O'Brien

4
@ IanR.O'Brien Flag1 | Cờ 2 dịch thành 001 hoặc 010 giống với 011, bây giờ nếu bạn thực hiện một đẳng thức của 011 == Flag1 hoặc dịch 011 == 001, sẽ trả về sai luôn. Bây giờ nếu bạn thực hiện một chút theo AND và với Flag1 thì nó chuyển thành 011 VÀ 001, trả về 001, hiện thực hiện đẳng thức trả về giá trị true, bởi vì 001 == 001
pqsk

Đây là giải pháp tốt nhất vì HasFlags sử dụng nhiều tài nguyên hơn. Và hơn nữa mọi thứ HasFlags đang làm, ở đây được thực hiện bởi trình biên dịch
Sebastian Xawery Wiśniowiecki

78

Đối với những người gặp khó khăn trong việc hình dung những gì đang xảy ra với giải pháp được chấp nhận (chính là điều này),

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do stuff.
}

testItem (theo câu hỏi) được định nghĩa là,

testItem 
 = flag1 | flag2  
 = 001 | 010  
 = 011

Sau đó, trong câu lệnh if, phía bên trái của phép so sánh là,

(testItem & flag1) 
 = (011 & 001) 
 = 001

Và câu lệnh if đầy đủ (đánh giá là đúng nếu flag1được đặt trong testItem),

(testItem & flag1) == flag1
 = (001) == 001
 = true

25

@ phil-devaney

Lưu ý rằng ngoại trừ trường hợp đơn giản nhất, Enum.HasFlag mang một hình phạt hiệu năng nặng so với việc viết mã bằng tay. Hãy xem xét các mã sau đây:

[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();
    }
}

Hơn 10 triệu lần lặp, phương pháp mở rộng HasFlags chiếm 4793 ms, so với 27 ms cho việc thực hiện theo chiều chuẩn bit.


5
Thật. Nếu bạn nhìn vào việc triển khai HasFlag, bạn sẽ thấy rằng nó thực hiện "GetType ()" trên cả hai toán hạng, khá chậm. Sau đó, nó "Enum.ToUInt64 (value.GetValue ());" trên cả hai toán hạng trước khi thực hiện kiểm tra bitwise.
dùng276648

1
Tôi đã chạy thử nghiệm của bạn nhiều lần và nhận được ~ 500ms cho HasFlags và ~ 32ms cho bitwise. Mặc dù vẫn là một thứ tự cường độ nhanh hơn với bitwise, HasFlags là một thứ tự giảm dần so với thử nghiệm của bạn. (Chạy thử nghiệm trên Core i3 và .NET 4.5 tốc độ 2,5 GHz)
MarioVW

1
@MarioVW Chạy nhiều lần trên .NET 4, i7-3770 cho ~ 2400ms so với ~ 20ms ở chế độ AnyCPU (64 bit) và ~ 3000ms so với ~ 20ms ở chế độ 32 bit. .NET 4.5 có thể đã tối ưu hóa nó một chút. Cũng lưu ý sự khác biệt về hiệu suất giữa các bản dựng 64 bit và 32 bit, có thể là do số học 64 bit nhanh hơn (xem nhận xét đầu tiên).
Bob

1
(«flag var» & «flag value») != 0không làm việc cho tôi. Điều kiện luôn luôn thất bại và trình biên dịch của tôi (Unity3D's Mono 2.6.5) báo cáo một cảnh báo của CS CS6262: Mã không thể truy cập được phát hiện ra khi sử dụng trong một if (…).
Slipp D. Thompson 2/2/2015

1
@ Wraith808: Tôi nhận ra lỗi đã xảy ra với bài kiểm tra của tôi rằng bạn đã sửa lỗi trong trò chơi của bạn, sức mạnh của 2 … = 1, … = 2, … = 4trên các giá trị enum rất quan trọng khi sử dụng [Flags]. Tôi đã giả định rằng nó sẽ tự động bắt đầu các mục vào 1và tiến lên bởi Po2s. Hành vi có vẻ nhất quán trên toàn bộ MS .NET và Unity's Mono. Tôi xin lỗi đặt điều này lên bạn.
Slipp D. Thompson

21

Tôi thiết lập một phương thức mở rộng để làm điều đó: câu hỏi liên quan .

Về cơ bản:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}

Sau đó, bạn có thể làm:

FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;

if( testItem.IsSet ( FlagTests.Flag1 ) )
    //Flag1 is set

Ngẫu nhiên quy ước tôi sử dụng cho enums là số ít cho tiêu chuẩn, số nhiều cho cờ. Bằng cách đó bạn biết từ tên enum cho dù nó có thể chứa nhiều giá trị.


Đây phải là một nhận xét nhưng vì tôi là người dùng mới nên có vẻ như tôi chưa thể thêm nhận xét nào ... bool Is static tĩnh (đầu vào Enum này, Enum matchTo) {return (Convert.ToUInt32 (đầu vào) & Convert .ToUInt32 (khớpTo))! = 0; } Có cách nào để tương thích với bất kỳ loại enum nào không (vì ở đây nó sẽ không hoạt động nếu enum của bạn thuộc loại UInt64 hoặc có thể có giá trị âm)?
dùng276648

Điều này khá dư thừa với Enum.HasFlag (Enum) (có sẵn trong .net 4.0)
PPC

1
@PPC Tôi sẽ không nói chính xác dự phòng - nhiều người đang phát triển trên các phiên bản cũ hơn của khung. Mặc dù vậy, bạn đã đúng, người dùng .Net 4 nên sử dụng HasFlagtiện ích mở rộng thay thế.
Keith

4
@Keith: Ngoài ra, có một sự khác biệt đáng chú ý: ((FlagTest) 0x1) .HasFlag (0x0) sẽ trở lại đúng, có thể hoặc không phải là hành vi mong muốn
PPC

19

Thêm một lời khuyên nữa ... Không bao giờ thực hiện kiểm tra nhị phân tiêu chuẩn với cờ có giá trị là "0". Kiểm tra của bạn trên cờ này sẽ luôn luôn đúng.

[Flags]
public enum LevelOfDetail
{
    [EnumMember(Value = "FullInfo")]
    FullInfo=0,
    [EnumMember(Value = "BusinessData")]
    BusinessData=1
}

Nếu bạn kiểm tra tham số đầu vào nhị phân so với FullInfo - bạn sẽ nhận được:

detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;

bPRez sẽ luôn luôn đúng như BẤT CỨ & 0 luôn == 0.


Thay vào đó, bạn chỉ cần kiểm tra xem giá trị của đầu vào là 0:

bool bPRez = (detailLevel == LevelOfDetail.FullInfo);

Tôi chỉ sửa một lỗi 0 cờ như vậy. Tôi nghĩ rằng đây là một lỗi thiết kế trong .NET framework (3.5) vì bạn cần biết giá trị cờ nào là 0 trước khi kiểm tra nó.
thersch

7
if((testItem & FlagTest.Flag1) == FlagTest.Flag1) 
{
...
}

5

Đối với các hoạt động bit, bạn cần sử dụng các toán tử bitwise.

Cái này cần phải dùng mẹo:

if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // Do something,
    // however This is never true.
}

Chỉnh sửa: Đã sửa lỗi kiểm tra nếu tôi kiểm tra lại - Tôi đã quay trở lại theo cách C / C ++ của mình (cảm ơn Ryan Farley vì đã chỉ ra)


5

Về chỉnh sửa. Bạn không thể làm cho nó đúng. Tôi đề nghị bạn gói những gì bạn muốn vào một lớp khác (hoặc phương thức mở rộng) để đến gần hơn với cú pháp bạn cần.

I E

public class FlagTestCompare
{
    public static bool Compare(this FlagTest myFlag, FlagTest condition)
    {
         return ((myFlag & condition) == condition);
    }
}

4

Thử cái này:


if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
    // do something
}
Về cơ bản, mã của bạn đang hỏi liệu có cả hai cờ được đặt giống như có một cờ được đặt hay không, điều này rõ ràng là sai. Đoạn mã trên sẽ chỉ để lại bit Flag1 nếu nó được đặt hoàn toàn, sau đó so sánh kết quả này với Flag1.


1

ngay cả khi không có [Cờ], bạn có thể sử dụng thứ gì đó như thế này

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}

hoặc nếu bạn có giá trị bằng không enum

if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}
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.