Làm cách nào để kiểm tra xem có bất kỳ cờ nào của tổ hợp cờ được đặt không?


180

Hãy nói rằng tôi có enum này:

[Flags]
enum Letters
{
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = A | B | C,
}

Để kiểm tra xem ví dụ đã ABđược đặt chưa, tôi có thể làm điều này:

if((letter & Letters.AB) == Letters.AB)

Có cách nào đơn giản hơn để kiểm tra xem có bất kỳ cờ nào trong hằng số cờ kết hợp được đặt so với sau không?

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Bạn có thể ví dụ trao đổi &với một cái gì đó?

Không quá ổn định khi nói đến những thứ nhị phân như thế này ...


Không nên đọc tất cả 'Tất cả = A | B | C '?
stevehipwell

4
AB | C tương đương với A | B | C vì AB được định nghĩa là A | B trước.
Daniel Brückner

1
@Daniel Brückner - Nó tương đương, nhưng nó ít đọc hơn. Đặc biệt là nếu enum được mở rộng.
stevehipwell

Thật. Tôi có thể thay đổi nó để đọc tốt hơn.
Svish

Câu trả lời:


145

Nếu bạn muốn biết nếu chữ cái có bất kỳ chữ cái nào trong AB thì bạn phải sử dụng toán tử AND &. Cái gì đó như:

if ((letter & Letters.AB) != 0)
{
    // Some flag (A,B or both) is enabled
}
else
{
    // None of them are enabled
}

2
Theo như tôi có thể thấy, đây là công việc. Và đã có những bình luận rõ ràng nhất. Không biên dịch mặc dù không có dấu ngoặc đơn xung quanh letter & Letters.AB. Chỉnh sửa rằng trong đó.
Svish

Ngoài ra nếu tôi giới thiệu một Letters.None, tôi giả sử bạn có thể trao đổi nó với 0một cái nhìn ít so sánh hơn với số ma thuật?
Svish

Tất nhiên. Nhưng tôi không nghĩ rằng so sánh AND với 0 có thể được coi là một con số kỳ diệu.
yeyeyerman

9
Ngoài ra stackoverflow.com/questions/40211/how-to-compare-flags-in-c là một câu trả lời được đề xuất khi nó kiểm tra đối với mục được đề cập thay vì kiểm tra xem nó có bằng 0
dan richardson

@danrichardson vấn đề với việc kiểm tra mục chính xác là nó loại bỏ trường hợp khi một phần của giá trị ghép được đặt (A, hoặc B), đó không phải là điều OP muốn.
Tom Lint

181

Trong .NET 4, bạn có thể sử dụng phương thức Enum.HasFlag :

using System;

[Flags] public enum Pet {
   None = 0,
   Dog = 1,
   Cat = 2,
   Bird = 4,
   Rabbit = 8,
   Other = 16
}

public class Example
{
   public static void Main()
   {
      // Define three families: one without pets, one with dog + cat and one with a dog only
      Pet[] petsInFamilies = { Pet.None, Pet.Dog | Pet.Cat, Pet.Dog };
      int familiesWithoutPets = 0;
      int familiesWithDog = 0;

      foreach (Pet petsInFamily in petsInFamilies)
      {
         // Count families that have no pets. 
         if (petsInFamily.Equals(Pet.None))
            familiesWithoutPets++;
         // Of families with pets, count families that have a dog. 
         else if (petsInFamily.HasFlag(Pet.Dog))
            familiesWithDog++;
      }
      Console.WriteLine("{0} of {1} families in the sample have no pets.", 
                        familiesWithoutPets, petsInFamilies.Length);   
      Console.WriteLine("{0} of {1} families in the sample have a dog.", 
                        familiesWithDog, petsInFamilies.Length);   
   }
}

Ví dụ hiển thị đầu ra sau:

//       1 of 3 families in the sample have no pets. 
//       2 of 3 families in the sample have a dog.

14
Điều này không giải quyết câu hỏi OP. Bạn vẫn phải && nhiều thao tác HasFlag để xác định xem cờ nào được đặt không. Vì vậy, câu hỏi là petsInFamilycó một Pet.Dog || Pet.Cat?
GoClimbColorado

1
Xem câu trả lời rõ ràng của ông Skeet ... HasFlags Nhiều
GoClimbColorado

59

Tôi sử dụng các phương thức mở rộng để viết những thứ như thế:

if (letter.IsFlagSet(Letter.AB))
    ...

Đây là mã:

public static class EnumExtensions
{
    private static void CheckIsEnum<T>(bool withFlags)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException(string.Format("Type '{0}' is not an enum", typeof(T).FullName));
        if (withFlags && !Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)))
            throw new ArgumentException(string.Format("Type '{0}' doesn't have the 'Flags' attribute", typeof(T).FullName));
    }

    public static bool IsFlagSet<T>(this T value, T flag) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flag);
        return (lValue & lFlag) != 0;
    }

    public static IEnumerable<T> GetFlags<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(true);
        foreach (T flag in Enum.GetValues(typeof(T)).Cast<T>())
        {
            if (value.IsFlagSet(flag))
                yield return flag;
        }
    }

    public static T SetFlags<T>(this T value, T flags, bool on) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = Convert.ToInt64(value);
        long lFlag = Convert.ToInt64(flags);
        if (on)
        {
            lValue |= lFlag;
        }
        else
        {
            lValue &= (~lFlag);
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static T SetFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, true);
    }

    public static T ClearFlags<T>(this T value, T flags) where T : struct
    {
        return value.SetFlags(flags, false);
    }

    public static T CombineFlags<T>(this IEnumerable<T> flags) where T : struct
    {
        CheckIsEnum<T>(true);
        long lValue = 0;
        foreach (T flag in flags)
        {
            long lFlag = Convert.ToInt64(flag);
            lValue |= lFlag;
        }
        return (T)Enum.ToObject(typeof(T), lValue);
    }

    public static string GetDescription<T>(this T value) where T : struct
    {
        CheckIsEnum<T>(false);
        string name = Enum.GetName(typeof(T), value);
        if (name != null)
        {
            FieldInfo field = typeof(T).GetField(name);
            if (field != null)
            {
                DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
                if (attr != null)
                {
                    return attr.Description;
                }
            }
        }
        return null;
    }
}

1
Bạn có thể làm cho nó chặt chẽ hơn một chút như vậy : where T : struct, IConvertible. Mã tuyệt vời khác!
Hamish Grubijan

@ HamishGrubijan, điểm tốt ... và enums cũng thực hiện IFormattable và IComparable. Tuy nhiên, tất cả các loại số cũng thực hiện các giao diện đó, vì vậy không đủ để loại trừ chúng
Thomas Levesque

Cảm ơn đã chia sẻ nhưng không phải lúc nào bạn cũng cần kiểm tra enum. IsFlagSet(this Enum value, Enum flag)là đủ.
djmj

34

Có phương pháp HasFlag trong .NET 4 trở lên.

if(letter.HasFlag(Letters.AB))
{
}

26

Nếu bạn có thể sử dụng .NET 4 hoặc cao hơn sử dụng phương thức HasFlag ()

ví dụ

letter.HasFlag(Letters.A | Letters.B) // both A and B must be set

giống như

letter.HasFlag(Letters.AB)

Bạn có chắc chắn bitwise ORlàm cho nó "cả hai phải được đặt" và không phải bất kỳ?
Chân đế

1
bitwise ORsẽ kết hợp các giá trị, vì vậy 1000 | 0010 trở thành 1010 hoặc cả hai được đặt
Armando

13

Nếu nó thực sự làm bạn khó chịu, bạn có thể viết một hàm như thế:

public bool IsSet(Letters value, Letters flag)
{
    return (value & flag) == flag;
}

if (IsSet(letter, Letters.A))
{
   // ...
}

// If you want to check if BOTH Letters.A and Letters.B are set:
if (IsSet(letter, Letters.A & Letters.B))
{
   // ...
}

// If you want an OR, I'm afraid you will have to be more verbose:
if (IsSet(letter, Letters.A) || IsSet(letter, Letters.B))
{
   // ...
}

1
Dòng return (value & flag) == flag;này không biên dịch: "Toán tử '&' không thể được áp dụng cho các toán hạng loại 'T' và 'T'" .
Fredrik Mörk

1
awe: Câu hỏi không phải là về các hoạt động nhị phân, câu hỏi là về việc đơn giản hóa cú pháp của các hoạt động liên quan đến bitmask trong C #. Có rất nhiều câu hỏi và câu trả lời liên quan đến hoạt động nhị phân tuyệt vời trên stackoverflow, không cần phải đăng lại chúng ở mọi nơi.
Tamas Czinege

Tôi nên khuyên những người không quen thuộc với các hoạt động nhị phân nên làm quen, vì giàn giáo để che giấu nó ở trên thực sự làm cho mọi thứ trở nên ít đọc hơn theo quan điểm của tôi. Tất nhiên, giải pháp 'thô' của tôi dưới đây hiện không hoạt động tốt so với điểm số của giải pháp này, vì vậy mọi người đang bỏ phiếu cho sở thích của họ và tôi phải tôn trọng điều đó ;-)
Will

10

Để kiểm tra xem ví dụ AB có được đặt không, tôi có thể làm điều này:

if ((chữ & Letters.AB) == Letters.AB)

Có cách nào đơn giản hơn để kiểm tra xem có bất kỳ cờ nào trong hằng số cờ kết hợp được đặt so với sau không?

Điều này kiểm tra rằng cả A và B đều được đặt và bỏ qua xem có bất kỳ cờ nào khác được đặt không.

if((letter & Letters.A) == Letters.A || (letter & Letters.B) == Letters.B)

Kiểm tra này hoặc A hoặc B được thiết lập, và bỏ qua cho dù bất kỳ cờ khác được thiết lập hay không.

Điều này có thể được đơn giản hóa để:

if(letter & Letters.AB)

Đây là C cho các hoạt động nhị phân; nên áp dụng điều này cho C #:

enum {
     A = 1,
     B = 2,
     C = 4,
     AB = A | B,
     All = AB | C,
};

int flags = A|C;

bool anything_and_a = flags & A;

bool only_a = (flags == A);

bool a_and_or_c_and_anything_else = flags & (A|C);

bool both_ac_and_anything_else = (flags & (A|C)) == (A|C);

bool only_a_and_c = (flags == (A|C));

Ngẫu nhiên, việc đặt tên của biến trong ví dụ của câu hỏi là "chữ cái" số ít, có thể ngụ ý rằng nó chỉ đại diện cho một chữ cái duy nhất; mã ví dụ cho thấy rõ rằng đó là một tập hợp các chữ cái có thể và nhiều giá trị được cho phép, vì vậy hãy xem xét đổi tên biến 'chữ cái'.


Sẽ không anything_and_a, a_and_or_c_and_anything_elseboth_ac_and_anything_elseluôn luôn đúng? hoặc tôi đang thiếu một cái gì đó ở đây?
Svish

Trong trường hợp này, bạn có thể thấy những lá cờ nào đã được khởi tạo. Tuy nhiên, nếu cờ không chứa A, thì (cờ & A) sẽ là 0, sai. Cả_ac_and_anything_else đảm bảo rằng cả A và C đều được đặt, nhưng bỏ qua mọi cờ khác cũng được đặt (ví dụ: đúng là B có được đặt hay không).
Sẽ

Hừm, một số trong số đó kết thúc dưới dạng số và không phải là boolean trong C #. Làm thế nào bạn sẽ chuyển đổi chúng để boolean?
Svish

Nó không ngầm chuyển đổi cho bạn? Không tương đương với 'false' và tất cả các giá trị khác là 'true'.
Sẽ

4

Làm thế nào về

if ((letter & Letters.AB) > 0)

?


Đúng! Điều này sẽ lọc trên các giá trị A và B và bỏ qua nếu C được bao gồm. Vì vậy, nếu nó> 0, nó cũng là A hoặc B hoặc AB.
kinh ngạc

3
Điều này không hoạt động 100% với các giá trị đã ký. ! = 0 tốt hơn> 0 vì lý do này.
stevehipwell

4

Tôi đã tạo một phương thức mở rộng đơn giản mà không cần kiểm tra Enumcác loại:

public static bool HasAnyFlag(this Enum value, Enum flags)
{
    return
        value != null && ((Convert.ToInt32(value) & Convert.ToInt32(flags)) != 0);
}

Nó cũng hoạt động trên enum nullable. HasFlagPhương thức chuẩn không có, vì vậy tôi cũng tạo một phần mở rộng để bao quát điều đó.

public static bool HasFlag(this Enum value, Enum flags)
{
    int f = Convert.ToInt32(flags);

    return
        value != null && ((Convert.ToInt32(value) & f) == f);
}

Một bài kiểm tra đơn giản:

[Flags]
enum Option
{
    None = 0x00,
    One = 0x01,
    Two = 0x02,
    Three = One | Two,
    Four = 0x04
}

[TestMethod]
public void HasAnyFlag()
{
    Option o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

[TestMethod]
public void HasAnyFlag_NullableEnum()
{
    Option? o1 = Option.One;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(false, o1.HasFlag(Option.Three));

    o1 |= Option.Two;
    Assert.AreEqual(true, o1.HasAnyFlag(Option.Three));
    Assert.AreEqual(true, o1.HasFlag(Option.Three));
}

Thưởng thức!


4

Có rất nhiều câu trả lời ở đây nhưng tôi nghĩ cách thành ngữ nhất để làm điều này với Cờ sẽ là Letters.AB.HasFlag (thư) hoặc (Letters.A | Letters.B) .HasFlag (thư) nếu bạn không đã có Letters.AB. letter.HasFlag (Letters.AB) chỉ hoạt động nếu nó có cả hai.


3

Liệu nó có giúp hiệu quả với anh không?

if ((letter & (Letters.A | Letters.B)) != 0)

Trân trọng,

Sebastiaan


1

Bạn có thể sử dụng phương thức mở rộng này trên enum, cho bất kỳ loại enum nào:

public static bool IsSingle(this Enum value)
{
    var items = Enum.GetValues(value.GetType());
    var counter = 0;
    foreach (var item in items)
    {
        if (value.HasFlag((Enum)item))
        {
            counter++;
        }
        if (counter > 1)
        {
            return false;
        }
    }
    return true;
}

0
if((int)letter != 0) { }

Bạn có thể mắc lỗi tương tự như tôi đã làm - anh ấy muốn kiểm tra xem A hoặc B đã được đặt chưa nhưng bỏ qua C.
Daniel Brückner

Bạn không cần diễn viên nếu bạn đang kiểm tra enum so với 0.
stevehipwell

Điều này sẽ kiểm tra xem có bất kỳ một trong số chúng được đặt không, nếu có bất kỳ enum kết hợp nào được đặt.
Svish

0

Bạn chỉ có thể kiểm tra nếu giá trị không bằng không.

if ((Int32)(letter & Letters.AB) != 0) { }

Nhưng tôi sẽ coi đó là một giải pháp tốt hơn để giới thiệu giá trị liệt kê mới với giá trị 0 và so sánh lại giá trị liệt kê này (nếu có thể vì bạn phải có thể sửa đổi bảng liệt kê).

[Flags]
enum Letters
{
    None = 0,
    A    = 1,
    B    = 2,
    C    = 4,
    AB   =  A | B,
    All  = AB | C
}

if (letter != Letters.None) { }

CẬP NHẬT

Đọc sai câu hỏi - đã sửa đề xuất đầu tiên và chỉ cần bỏ qua đề xuất thứ hai.


Bạn không cần diễn viên nếu bạn đang kiểm tra enum so với 0.
stevehipwell

0

Có hai aproaches mà tôi có thể thấy sẽ hoạt động để kiểm tra xem có bit nào được đặt không.

Aproach A

if (letter != 0)
{
}

Điều này hoạt động miễn là bạn không ngại kiểm tra tất cả các bit, bao gồm cả các bit không được xác định quá!

Aproach B

if ((letter & Letters.All) != 0)
{
}

Điều này chỉ kiểm tra các bit được xác định, miễn là Letters.All đại diện cho tất cả các bit có thể.

Đối với các bit cụ thể (một hoặc nhiều bộ), hãy sử dụng Aproach B thay thế Letters.All với các bit mà bạn muốn kiểm tra (xem bên dưới).

if ((letter & Letters.AB) != 0)
{
}

Bạn có thể mắc lỗi tương tự như tôi đã làm - anh ấy muốn kiểm tra xem A hoặc B đã được đặt chưa nhưng bỏ qua C.
Daniel Brückner

-1

Xin lỗi, nhưng tôi sẽ hiển thị nó trong VB :)

   <Flags()> Public Enum Cnt As Integer
        None = 0
        One = 1
        Two = 2
        Three = 4
        Four = 8    
    End Enum

    Sub Test()
    Dim CntValue As New Cnt
    CntValue += Cnt.One
    CntValue += Cnt.Three
    Console.WriteLine(CntValue)
    End Sub

CntValue = 5 Vậy enum chứa 1 + 4

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.