จะตรวจสอบว่ามีการตั้งค่าสถานะของชุดค่าสถานะใด ๆ ได้อย่างไร


180

สมมติว่าฉันมี enum นี้:

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

หากต้องการตรวจสอบว่าABมีการตั้งค่าตัวอย่างฉันสามารถทำสิ่งนี้:

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

มีวิธีที่ง่ายกว่าหรือไม่ในการตรวจสอบว่ามีการตั้งค่าสถานะใด ๆ ของค่าคงที่รวมกันมากกว่าค่าต่อไปนี้หรือไม่?

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

ตัวอย่างเช่นคุณสามารถแลกเปลี่ยน&กับบางสิ่ง?

ไม่มั่นคงเกินไปเมื่อพูดถึงไบนารีสิ่งเช่นนี้ ...


ไม่ควรอ่าน 'ทั้งหมด = A | B | ค'?
stevehipwell

4
AB | C เทียบเท่ากับ A | B | C เพราะ AB ถูกกำหนดเป็น A | B มาก่อน
Daniel Brückner

1
@Daniel Brückner - มันเทียบเท่า แต่อ่านได้น้อยกว่า โดยเฉพาะอย่างยิ่งถ้า enum ถูกขยาย
stevehipwell

จริง ฉันสามารถเปลี่ยนเพื่อการอ่านที่ดีขึ้น
Svish

คำตอบ:


145

หากคุณต้องการที่จะทราบว่าตัวอักษรมีใด ๆ ของตัวอักษรใน AB คุณต้องใช้และและผู้ประกอบการ สิ่งที่ต้องการ:

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

2
เท่าที่ฉันเห็นนี่เป็นงาน และมีความคิดเห็นที่ชัดเจนที่สุด letter & Letters.ABไม่ได้รวบรวมแม้ว่าจะไม่มีรอบวงเล็บ แก้ไขที่นั่น
Svish

นอกจากนี้หากฉันแนะนำ a Letters.Noneฉันคิดว่าคุณสามารถสลับมันด้วย0รูปลักษณ์ที่มีการเปรียบเทียบกับเวทมนตร์น้อยลงได้หรือไม่?
Svish

แน่นอน. แต่ฉันไม่คิดว่าการเปรียบเทียบ AND กับ 0 สามารถคิดได้ว่าเป็นหมายเลขเวทย์มนตร์อย่างเคร่งครัด
yeyeyerman

9
ยังstackoverflow.com/questions/40211/how-to-compare-flags-in-cเป็นคำตอบที่แนะนำเท่าที่จะตรวจสอบกับรายการในคำถามที่ตรงข้ามกับการตรวจสอบถ้ามันเท่ากับ 0
แดนริชาร์ด

@danrichardson มีปัญหากับการตรวจสอบรายการที่แน่นอนคือมันกำจัดกรณีเมื่อส่วนหนึ่งของค่าผสมถูกตั้งค่า (ทั้ง A หรือ B) ซึ่งไม่ใช่สิ่งที่ OP ต้องการ
Tom Lint

181

ใน. NET 4 คุณสามารถใช้วิธี 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);   
   }
}

ตัวอย่างแสดงผลลัพธ์ต่อไปนี้:

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

14
นี่ไม่ได้ตอบคำถาม OP คุณต้องยังคง && การทำงานของ HasFlag หลายรายการเพื่อตรวจสอบว่ามีการตั้งค่าสถานะใด ๆ หรือไม่ ดังนั้นคำถามคือpetsInFamilyมีทั้งPet.Dog || Pet.Cat?
GoClimbColorado

1
ดูคำตอบที่ชัดเจนของ Mr. Skeet ... HasFlags Multiple
GoClimbColorado

59

ฉันใช้วิธีการส่วนขยายเพื่อเขียนสิ่งต่าง ๆ เช่น:

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

นี่คือรหัส:

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
where T : struct, IConvertibleคุณสามารถทำให้มันเป็นบิตที่เข้มงวดมากขึ้นเพื่อต้องการ: รหัสที่ยอดเยี่ยมเป็นอย่างอื่น!
Hamish Grubijan

@ HamishGrubijan จุดดี ... และ enums ยังใช้ IFormattable และ IComparable อย่างไรก็ตามประเภทตัวเลขทั้งหมดใช้อินเทอร์เฟซเหล่านั้นด้วยดังนั้นจึงไม่เพียงพอที่จะยกเว้นพวกเขา
Thomas Levesque

ขอบคุณสำหรับการแบ่งปัน แต่คุณไม่จำเป็นต้องตรวจสอบ enum เสมอไป IsFlagSet(this Enum value, Enum flag)เพียงพอแล้ว
djmj


26

หากคุณสามารถใช้. NET 4 หรือสูงกว่าใช้วิธี HasFlag ()

ตัวอย่าง

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

เหมือนกับ

letter.HasFlag(Letters.AB)

คุณแน่ใจหรือว่าbitwise ORทำให้ "ทั้งสองต้องตั้งค่า" และไม่ใช่หรือไม่?
วงเล็บ

1
bitwise ORจะรวมค่าดังนั้น 1,000 | 0010 กลายเป็น 1010 หรือทั้งชุด
อาร์มันโด

13

ถ้ามันทำให้คุณรำคาญจริงๆคุณสามารถเขียนฟังก์ชั่นแบบนั้นได้:

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
สายreturn (value & flag) == flag;ไม่ได้รวบรวม: "ผู้ประกอบการ '&' ไม่สามารถนำไปใช้กับตัวถูกดำเนินการชนิด 'T' และ 'T'"
Fredrik Mörk

1
awe: คำถามไม่ได้เกี่ยวกับการทำงานของไบนารี แต่คำถามก็คือการทำให้ไวยากรณ์ของการดำเนินการที่เกี่ยวข้องกับ bitmask ง่ายขึ้นใน C # มีคำถามและคำตอบเกี่ยวกับการดำเนินการไบนารีที่ยอดเยี่ยมมากมายเกี่ยวกับสแต็คโอเวอร์โฟลว์อยู่แล้วไม่จำเป็นต้องทำการโพสต์ซ้ำทุกที่
Tamas Czinege

ฉันควรแนะนำว่าคนที่ไม่คุ้นเคยกับการทำงานแบบไบนารีจะคุ้นเคยเพราะการนั่งร้านเพื่อซ่อนมันไว้ข้างต้นทำให้สิ่งที่ฉันอ่านไม่ออก แน่นอนโซลูชันของฉัน 'ดิบ' ด้านล่างอยู่ในขณะนี้ไม่ได้ทำเพื่อให้ดีเมื่อเทียบกับคะแนนของการแก้ปัญหานี้เพื่อให้คนมีการลงคะแนนความชอบของพวกเขาและฉันต้องเคารพว่า ;-)
Will

10

ในการตรวจสอบว่ามีการตั้งค่าตัวอย่าง AB ฉันสามารถทำได้:

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

มีวิธีที่ง่ายกว่าหรือไม่ในการตรวจสอบว่ามีการตั้งค่าสถานะใด ๆ ของค่าคงที่รวมกันมากกว่าค่าต่อไปนี้หรือไม่?

สิ่งนี้จะตรวจสอบว่ามีการตั้งค่าทั้ง A และ B และไม่สนใจว่าจะตั้งค่าสถานะอื่นใดหรือไม่

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

การตรวจสอบนี้ว่าทั้ง A หรือ B มีการตั้งค่าและการละเว้นไม่ว่าจะเป็นธงอื่น ๆ ที่มีการตั้งค่าหรือไม่

สิ่งนี้สามารถทำให้ง่ายขึ้นเพื่อ:

if(letter & Letters.AB)

นี่คือ C สำหรับการดำเนินการแบบไบนารี ควรนำสิ่งนี้ไปใช้กับ 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));

อนึ่งการตั้งชื่อของตัวแปรในตัวอย่างของคำถามคือ 'ตัวอักษร' เอกพจน์ซึ่งอาจบอกเป็นนัยว่ามันหมายถึงตัวอักษรเพียงตัวเดียว โค้ดตัวอย่างทำให้ชัดเจนว่าเป็นชุดตัวอักษรที่เป็นไปได้และอนุญาตให้ใช้หลายค่าได้ดังนั้นให้พิจารณาเปลี่ยนชื่อตัวแปร 'ตัวอักษร'


จะไม่anything_and_a, a_and_or_c_and_anything_elseและboth_ac_and_anything_elseมักจะเป็นจริงหรือ? หรือฉันกำลังพลาดอะไรบางอย่างที่นี่?
Svish

ในกรณีนี้คุณสามารถดูว่ามีการเริ่มต้นการตั้งค่าสถานะใด อย่างไรก็ตามควรตั้งค่าสถานะไม่ประกอบด้วย A ดังนั้น (ธง & A) จะเป็น 0 ซึ่งเป็นเท็จ both_ac_and_anything_else ทำให้แน่ใจว่าทั้ง A และ C ถูกตั้งค่าไว้ แต่ไม่สนใจแฟล็กอื่นใดที่ตั้งค่าไว้ (เช่นความจริงไม่ว่าจะตั้งค่า B หรือไม่ก็ตาม)
จะ

หืมบางคนกลายมาเป็นตัวเลขและไม่ได้บูลีนใน C # คุณจะแปลงมันเป็นบูลีนได้อย่างไร?
Svish

มันไม่ได้ถูกแปลงโดยปริยายสำหรับคุณ? ซีโร่เท่ากับ 'เท็จ' และค่าอื่น ๆ ทั้งหมดเป็น 'จริง'
จะ

4

เกี่ยวกับ

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

?


ใช่ สิ่งนี้จะกรองค่า A และ B และละเว้นถ้ารวม C ดังนั้นถ้ามันเป็น> 0 ก็เป็น A หรือ B หรือ AB
กลัว

3
สิ่งนี้ไม่ทำงาน 100% กับค่าที่เซ็นชื่อ ! = 0 ดีกว่า> 0 ด้วยเหตุผลนี้
stevehipwell

4

ฉันสร้างวิธีการขยายอย่างง่ายที่ไม่จำเป็นต้องตรวจสอบEnumประเภท:

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

นอกจากนี้ยังทำงานบน enums nullable HasFlagวิธีการมาตรฐานไม่ได้ดังนั้นฉันจึงสร้างส่วนขยายเพื่อให้ครอบคลุมเช่นกัน

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

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

การทดสอบอย่างง่าย:

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

สนุก!


4

มีคำตอบมากมายอยู่ที่นี่ แต่ฉันคิดว่าวิธีที่งี่เง่าที่สุดในการทำเช่นนี้กับ Flags จะเป็น Letters มี Letters.AB แล้ว ตัวอักษรHasFlag (Letters.AB) ใช้ได้เฉพาะเมื่อมันมีทั้งคู่



1

คุณสามารถใช้วิธีการขยายนี้กับ enum สำหรับ enums ประเภทใดก็ได้:

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) { }

คุณอาจผิดพลาดแบบเดียวกับที่ฉันทำ - เขาต้องการตรวจสอบว่ามีการตั้งค่า A หรือ B แต่ไม่สนใจ C.
Daniel Brückner

คุณไม่ต้องการนักแสดงถ้าคุณกำลังตรวจสอบค่า enum เทียบกับ 0
stevehipwell

การดำเนินการนี้จะตรวจสอบว่ามีการตั้งค่าใด ๆ ทั้งหมดหรือไม่หากมีการตั้งค่า enum ที่รวมอยู่
Svish

0

คุณสามารถตรวจสอบว่าค่าไม่เป็นศูนย์หรือไม่

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

แต่ฉันคิดว่านี่เป็นวิธีแก้ปัญหาที่ดีกว่าในการแนะนำค่าการแจงนับใหม่กับค่าศูนย์และเปรียบเทียบความคุ้มค่าการแจงนับนี้ (ถ้าเป็นไปได้เพราะคุณต้องสามารถแก้ไขการแจงนับได้)

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

if (letter != Letters.None) { }

UPDATE

Missread คำถาม - แก้ไขข้อเสนอแนะแรกและเพียงละเว้นข้อเสนอแนะที่สอง


คุณไม่ต้องการนักแสดงถ้าคุณกำลังตรวจสอบค่า enum เทียบกับ 0
stevehipwell

0

มีสองเรื่องที่ฉันเห็นว่าสามารถใช้ตรวจสอบบิตใด ๆ ที่ตั้งค่าไว้ได้

Aproach A

if (letter != 0)
{
}

ทำงานได้ตราบใดที่คุณไม่สนใจการตรวจสอบบิตทั้งหมดรวมถึงบิตที่ไม่ได้กำหนดเช่นกัน!

Aproach B

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

สิ่งนี้จะตรวจสอบบิตที่กำหนดตราบใดที่ Letters.All จะแทนบิตที่เป็นไปได้ทั้งหมด

สำหรับบิตเฉพาะ (หนึ่งชุดขึ้นไป) ให้ใช้ Aproach B แทน Letters.All ด้วยบิตที่คุณต้องการตรวจสอบ (ดูด้านล่าง)

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

คุณอาจผิดพลาดแบบเดียวกับที่ฉันทำ - เขาต้องการตรวจสอบว่า A หรือ B ตั้งค่าไว้หรือไม่ แต่ไม่สนใจ C.
Daniel Brückner

-1

ขออภัย แต่ฉันจะแสดงใน 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 ดังนั้น enum มี 1 + 4

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