การดำเนินการ C # bitwise ที่ใช้กันทั่วไปส่วนใหญ่


201

สำหรับชีวิตของฉันฉันจำไม่ได้ว่าจะตั้งลบสลับหรือทดสอบบิตในบิตฟิลด์ ฉันไม่แน่ใจหรือผสมกันเพราะฉันไม่ค่อยต้องการสิ่งเหล่านี้ ดังนั้น "บิตโกงแผ่น" น่าจะมี

ตัวอย่างเช่น:

flags = flags | FlagsEnum.Bit4;  // Set bit 4.

หรือ

if ((flags & FlagsEnum.Bit4)) == FlagsEnum.Bit4) // Is there a less verbose way?

คุณสามารถยกตัวอย่างการดำเนินงานทั่วไปอื่น ๆ ทั้งหมดโดยเฉพาะอย่างยิ่งในไวยากรณ์ C # โดยใช้ [Flags] enum หรือไม่


5
สิ่งนี้ได้รับคำตอบก่อนหน้านี้
Greg Rogers

7
น่าเสียดายที่ลิงก์ไม่ปรากฏในคำใบ้คำถามสำหรับหัวข้อนี้
Cori

10
อย่างไรก็ตามมีการติดแท็กคำถามไว้สำหรับ c / c ++ ดังนั้นบางคนที่ค้นหาข้อมูลเกี่ยวกับ C # อาจไม่ได้ดูที่นั่นแม้ว่าไวยากรณ์ดูเหมือนจะเหมือนกัน
Adam Lassek

ฉันไม่ทราบวิธีที่น้อยกว่าในการทำแบบทดสอบบิต
Andy Johnson

2
@Andy มี API สำหรับทดสอบบิตใน. NET 4 ในขณะนี้
Drew Noakes

คำตอบ:


288

ฉันทำงานส่วนขยายเหล่านี้เพิ่มเติม - คุณสามารถค้นหารหัสได้ที่นี่

ฉันเขียนวิธีการขยายบางอย่างที่ขยาย System.Enum ที่ฉันใช้บ่อย ... ฉันไม่ได้อ้างว่ามันเป็นกระสุน แต่พวกเขาช่วย ... ความคิดเห็นลบ ...

namespace Enum.Extensions {

    public static class EnumerationExtensions {

        public static bool Has<T>(this System.Enum type, T value) {
            try {
                return (((int)(object)type & (int)(object)value) == (int)(object)value);
            } 
            catch {
                return false;
            }
        }

        public static bool Is<T>(this System.Enum type, T value) {
            try {
                return (int)(object)type == (int)(object)value;
            }
            catch {
                return false;
            }    
        }


        public static T Add<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type | (int)(object)value));
            }
            catch(Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not append value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }    
        }


        public static T Remove<T>(this System.Enum type, T value) {
            try {
                return (T)(object)(((int)(object)type & ~(int)(object)value));
            }
            catch (Exception ex) {
                throw new ArgumentException(
                    string.Format(
                        "Could not remove value from enumerated type '{0}'.",
                        typeof(T).Name
                        ), ex);
            }  
        }

    }
}

จากนั้นพวกเขาจะใช้ดังต่อไปนี้

SomeType value = SomeType.Grapes;
bool isGrapes = value.Is(SomeType.Grapes); //true
bool hasGrapes = value.Has(SomeType.Grapes); //true

value = value.Add(SomeType.Oranges);
value = value.Add(SomeType.Apples);
value = value.Remove(SomeType.Grapes);

bool hasOranges = value.Has(SomeType.Oranges); //true
bool isApples = value.Is(SomeType.Apples); //false
bool hasGrapes = value.Has(SomeType.Grapes); //false

1
ฉันพบว่ามีประโยชน์เช่นนี้ - ความคิดใด ๆ ที่ฉันสามารถแก้ไขเพื่อให้ทำงานกับประเภทพื้นฐานได้
Charlie Salts

7
ส่วนขยายเหล่านี้ทำวันของฉันสัปดาห์ของฉันเดือนของฉันและอาจเป็นปีของฉัน
thaBadDawg

ขอบคุณ! ทุกคน: อย่าลืมตรวจสอบอัปเดตที่ Hugoware เชื่อมโยงด้วย
Helge Klein

ชุดส่วนขยายที่ดีมาก มันเป็นความอัปยศที่พวกเขาต้องการการชกมวยแม้ว่าฉันจะนึกถึงตัวเลือกอื่นที่ไม่ได้ใช้มวยและเป็นรวบรัดนี้ แม้แต่HasFlagวิธีการใหม่ที่Enumต้องใช้มวย
Drew Noakes

4
@Drew: ดูcode.google.com/p/unconstrained-melodyหาวิธีในการหลีกเลี่ยงมวย :)
จอนสกีต

109

ใน. NET 4 ตอนนี้คุณสามารถเขียน:

flags.HasFlag(FlagsEnum.Bit4)

4
+1 สำหรับการชี้ให้เห็นแม้ว่าFlagsEnumจะเป็นชื่อที่น่าเกลียด :)
Jim Schubert

2
@ จิมบางที มันเป็นเพียงชื่อตัวอย่างที่ใช้ในคำถามเดิมดังนั้นคุณสามารถเปลี่ยนเป็นรหัสได้
Drew Noakes

14
ฉันรู้ว่า! แต่ชื่อที่น่าเกลียดเป็นเหมือน IE6 และอาจจะไม่หายไป :(
จิมชูเบิร์ต

5
@ JimSchubert อีกครั้งฉันเพิ่งพิมพ์ชื่อประเภทจากคำถามเดิมเพื่อไม่ให้เกิดความสับสน .NET ประเภทการแจงนับหลักเกณฑ์การตั้งชื่อระบุว่าทั้งหมด[Flags]enums ควรมีชื่อ pluralised ดังนั้นชื่อFlagsEnumมีแม้กระทั่งปัญหาร้ายแรงกว่าความอัปลักษณ์
Drew Noakes

1
ฉันยังแนะนำแนวทางการออกแบบ Framework: Conventions, Idioms และ Patterns สำหรับ. NET Library ที่นำกลับมาใช้ใหม่ได้ มันแพงไปหน่อยที่จะซื้อ แต่ฉันเชื่อว่า Safari Online และ Books24x7 ทั้งคู่เสนอให้สำหรับสมาชิก
จิมชูเบิร์ต

89

สำนวนคือการใช้ตัวดำเนินการ bitwise หรือเท่ากันเพื่อตั้งบิต:

flags |= 0x04;

เพื่อล้างบิต, สำนวนคือการใช้บิตและด้วยการปฏิเสธ:

flags &= ~0x04;

บางครั้งคุณมีออฟเซ็ตที่ระบุบิตของคุณแล้วสำนวนคือการใช้สิ่งเหล่านี้รวมกับการเปลี่ยนแปลงซ้าย:

flags |= 1 << offset;
flags &= ~(1 << offset);

22

@Drew

โปรดทราบว่ายกเว้นในกรณีที่ง่ายที่สุด Enum.HasFlag มีบทลงโทษที่หนักหน่วงเมื่อเปรียบเทียบกับการเขียนโค้ดด้วยตนเอง พิจารณารหัสต่อไปนี้:

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

การทำซ้ำกว่า 10 ล้านครั้งวิธีการขยาย HasFlags ใช้เวลาถึง 4793 มิลลิวินาทีเมื่อเทียบกับ 27 มิลลิวินาทีสำหรับการใช้งานระดับบิตมาตรฐาน


10
ในขณะที่น่าสนใจและดีที่จะชี้ให้เห็นอย่างแน่นอน คุณจำเป็นต้องพิจารณาการใช้งาน ตามนี้ถ้าคุณไม่ได้แสดงสองแสนหรือมากกว่า ops คุณอาจไม่ได้สังเกตเห็นสิ่งนี้
Joshua Hayes

7
HasFlagวิธีการที่เกี่ยวข้องกับมวย / unboxing ที่บัญชีสำหรับความแตกต่างนี้ แต่ค่าใช้จ่ายนั้นเล็กน้อยมาก (0.4) ที่ว่าถ้าคุณไม่อยู่ในวงแคบ ๆ ฉันจะใช้ API ที่เปิดเผย (และมีโอกาสน้อยกว่า) ในการอ่านได้ทุกวัน
Drew Noakes

8
มันอาจเป็นปัญหา และเนื่องจากฉันทำงานกับรถตักค่อนข้างน้อยฉันคิดว่ามันเป็นการดีที่จะชี้ให้เห็น
Chuck Dee

11

การดำเนินการ enum ค่าสถานะของ. NET นั้นค่อนข้าง จำกัด เวลาส่วนใหญ่ที่ผู้ใช้เหลืออยู่ด้วยการหาตรรกะการดำเนินการระดับบิต

ใน. NET 4 วิธีการที่HasFlagเพิ่มเข้ามาEnumซึ่งช่วยให้รหัสของผู้ใช้ง่ายขึ้น แต่น่าเสียดายที่มีปัญหามากมายกับมัน

  1. HasFlag ไม่ปลอดภัยสำหรับประเภทเพราะยอมรับข้อโต้แย้งค่า enum ทุกประเภทไม่ใช่เฉพาะประเภท enum ที่กำหนด
  2. HasFlagคลุมเครือว่าจะตรวจสอบหรือไม่ว่าค่านั้นมีค่าสถานะทั้งหมดหรือใด ๆ ที่ระบุโดยอาร์กิวเมนต์ค่า enum มันคือทั้งหมดที่โดยวิธีการ
  3. HasFlag ค่อนข้างช้าเนื่องจากต้องใช้มวยซึ่งเป็นสาเหตุของการจัดสรรและทำให้มีการรวบรวมขยะมากขึ้น

เนื่องจากส่วนหนึ่งของ. NET ที่สนับสนุนอย่าง จำกัด สำหรับการกำหนดค่าสถานะฉันเขียนไลบรารี OSS Enums.NETซึ่งจัดการปัญหาเหล่านี้แต่ละรายการและทำให้การจัดการกับค่าสถานะ enums ง่ายขึ้นมาก

ด้านล่างนี้คือการดำเนินการบางอย่างที่มีให้พร้อมกับการใช้งานที่เทียบเท่าโดยใช้เพียงกรอบงาน. NET

รวมธง

.สุทธิ             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


ลบค่าสถานะ

.สุทธิ             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


ธงทั่วไป

.สุทธิ             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


สลับค่าสถานะ

.สุทธิ             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


มีธงทั้งหมด

. NET             (flags & otherFlags) == otherFlagsหรือflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


มีธง

.สุทธิ             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


รับค่าสถานะ

.สุทธิ

Enumerable.Range(0, 64)
  .Where(bit => ((flags.GetTypeCode() == TypeCode.UInt64 ? (long)(ulong)flags : Convert.ToInt64(flags)) & (1L << bit)) != 0)
  .Select(bit => Enum.ToObject(flags.GetType(), 1L << bit))`

Enums.NET flags.GetFlags()


ฉันพยายามรับการปรับปรุงเหล่านี้รวมอยู่ใน. NET Core และอาจเป็น. NET Framework แบบเต็ม คุณสามารถตรวจสอบข้อเสนอของฉันที่นี่


7

ไวยากรณ์ C ++ สมมติว่าบิต 0 คือ LSB สมมติว่าแฟล็กไม่ได้ลงนามนาน:

ตรวจสอบว่าตั้ง:

flags & (1UL << (bit to test# - 1))

ตรวจสอบว่าไม่ได้ตั้งค่า:

invert test !(flag & (...))

ตั้ง:

flag |= (1UL << (bit to set# - 1))

ชัดเจน:

flag &= ~(1UL << (bit to clear# - 1))

สลับ:

flag ^= (1UL << (bit to set# - 1))

3

เพื่อประสิทธิภาพที่ดีที่สุดและไม่มีขยะให้ใช้สิ่งนี้:

using System;
using T = MyNamespace.MyFlags;

namespace MyNamespace
{
    [Flags]
    public enum MyFlags
    {
        None = 0,
        Flag1 = 1,
        Flag2 = 2
    }

    static class MyFlagsEx
    {
        public static bool Has(this T type, T value)
        {
            return (type & value) == value;
        }

        public static bool Is(this T type, T value)
        {
            return type == value;
        }

        public static T Add(this T type, T value)
        {
            return type | value;
        }

        public static T Remove(this T type, T value)
        {
            return type & ~value;
        }
    }
}

2

ในการทดสอบบิตคุณต้องทำดังนี้: (สมมติว่าค่าสถานะเป็นตัวเลข 32 บิต)

ทดสอบบิต:

if((flags & 0x08) == 0x08)
(หากตั้งค่าบิต 4 ไว้จริง) สลับกลับ (1 - 0 หรือ 0 - 1):
flags = flags ^ 0x08;
รีเซ็ต Bit 4 เป็นศูนย์:
flags = flags & 0xFFFFFF7F;


2
-1 เนื่องจากสิ่งนี้ไม่ได้รบกวน Enums ด้วยเหรอ? นอกจากนี้การเขียนโค้ดด้วยมือมีค่าเปราะบาง ... อย่างน้อยฉันก็ควรจะเขียน~0x08แทน0xFFFFFFF7... (มาสก์จริงสำหรับ 0x8)
Ben Mosher

1
ตอนแรกฉันคิดว่าเบ็น -1 นั้นรุนแรง แต่การใช้ "0xFFFFFF7F" ทำให้ตัวอย่างนี้แย่เป็นพิเศษ
ToolmakerSteve

2

สิ่งนี้ได้รับแรงบันดาลใจจากการใช้ Sets as indexers ใน Delphi ย้อนกลับไปเมื่อ:

/// Example of using a Boolean indexed property
/// to manipulate a [Flags] enum:

public class BindingFlagsIndexer
{
  BindingFlags flags = BindingFlags.Default;

  public BindingFlagsIndexer()
  {
  }

  public BindingFlagsIndexer( BindingFlags value )
  {
     this.flags = value;
  }

  public bool this[BindingFlags index]
  {
    get
    {
      return (this.flags & index) == index;
    }
    set( bool value )
    {
      if( value )
        this.flags |= index;
      else
        this.flags &= ~index;
    }
  }

  public BindingFlags Value 
  {
    get
    { 
      return flags;
    } 
    set( BindingFlags value ) 
    {
      this.flags = value;
    }
  }

  public static implicit operator BindingFlags( BindingFlagsIndexer src )
  {
     return src != null ? src.Value : BindingFlags.Default;
  }

  public static implicit operator BindingFlagsIndexer( BindingFlags src )
  {
     return new BindingFlagsIndexer( src );
  }

}

public static class Class1
{
  public static void Example()
  {
    BindingFlagsIndexer myFlags = new BindingFlagsIndexer();

    // Sets the flag(s) passed as the indexer:

    myFlags[BindingFlags.ExactBinding] = true;

    // Indexer can specify multiple flags at once:

    myFlags[BindingFlags.Instance | BindingFlags.Static] = true;

    // Get boolean indicating if specified flag(s) are set:

    bool flatten = myFlags[BindingFlags.FlattenHierarchy];

    // use | to test if multiple flags are set:

    bool isProtected = ! myFlags[BindingFlags.Public | BindingFlags.NonPublic];

  }
}

2
สิ่งนี้แม้จะไม่ได้รวบรวมหาก BindingFlags เป็น byte enum: this.flags & = ~ index;
amuliar

0

การดำเนินงาน C ++ คือ: & | ^ ~ (สำหรับและ, หรือ, xor และไม่ใช่การทำงานระดับบิต) สิ่งที่น่าสนใจก็คือ >> และ << ซึ่งเป็นการดำเนินการบิตบิท

ดังนั้นในการทดสอบการตั้งค่าบิตในแฟล็กคุณควรใช้: ถ้า (ค่าสถานะ & 8) // การทดสอบบิตที่ 4 ได้รับการตั้งค่า


8
คำถามเกี่ยวข้องกับ c # ไม่ใช่ c ++
Andy Johnson

3
ในทางกลับกัน C # ใช้ตัวดำเนินการเดียวกัน: msdn.microsoft.com/en-us/library/6a71f45d.aspx
ToolmakerSteve

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