Các hoạt động bitwise C # phổ biến nhất trên enums


201

Đối với cuộc sống của tôi, tôi không thể nhớ cách thiết lập, xóa, chuyển đổi hoặc kiểm tra một chút trong bitfield. Hoặc là tôi không chắc chắn hoặc tôi trộn chúng lên vì tôi hiếm khi cần những thứ này. Vì vậy, một "bit-cheat-sheet" sẽ rất tốt để có.

Ví dụ:

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

hoặc là

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

Bạn có thể đưa ra ví dụ về tất cả các hoạt động phổ biến khác, tốt nhất là theo cú pháp C # bằng cách sử dụng enum [Cờ] không?


5
Điều này đã được trả lời trước đây
Greg Rogers

7
quá tệ khi liên kết không xuất hiện trong các gợi ý câu hỏi cho chủ đề này.
cori

10
Tuy nhiên, câu hỏi đó được gắn thẻ cho c / c ++, vì vậy ai đó đang tìm kiếm thông tin về C # có thể sẽ không nhìn vào đó mặc dù cú pháp có vẻ giống nhau.
Adam Lassek

Tôi không biết một cách ít dài dòng hơn để làm bài kiểm tra bit
Andy Johnson

2
@Andy, hiện đã có API cho bài kiểm tra bit trong .NET 4.
vẽ Noakes

Câu trả lời:


288

Tôi đã thực hiện thêm một số công việc trên các tiện ích mở rộng này - Bạn có thể tìm thấy mã ở đây

Tôi đã viết một số phương thức mở rộng mở rộng System.Enum mà tôi thường sử dụng ... Tôi không cho rằng chúng có khả năng chống đạn, nhưng chúng đã giúp ... Nhận xét đã bị xóa ...

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

    }
}

Sau đó, chúng được sử dụng như sau

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
Tôi cũng thấy điều này hữu ích - Bất kỳ ý tưởng nào tôi có thể sửa đổi nó để nó hoạt động trên bất kỳ loại cơ bản nào?
Charlie Salts

7
Các tiện ích mở rộng này chỉ làm cho ngày của tôi, tuần của tôi, tháng của tôi và hoàn toàn có thể là năm của tôi.
thaBadDawg

Cảm ơn bạn! Mọi người: hãy chắc chắn kiểm tra bản cập nhật mà Hugoware đã liên kết đến.
Helge Klein

Một bộ tiện ích mở rộng rất đẹp. Thật xấu hổ khi họ yêu cầu quyền anh, mặc dù tôi không thể nghĩ ra một giải pháp thay thế không sử dụng quyền anh và đây là sự cô đọng. Ngay cả HasFlagphương pháp mới trên Enumđòi hỏi quyền anh.
vẽ Noakes

4
@Drew: Xem code.google.com/p/unconstrained-melody để biết cách tránh quyền anh :)
Jon Skeet

109

Trong .NET 4 bây giờ bạn có thể viết:

flags.HasFlag(FlagsEnum.Bit4)

4
+1 để chỉ ra rằng, mặc dù FlagsEnumlà một cái tên xấu xí. :)
Jim Schubert

2
@Jim, có lẽ. Đây chỉ là một tên mẫu, như được sử dụng trong câu hỏi ban đầu, vì vậy bạn có thể tự do thay đổi nó trong mã của mình.
vẽ Noakes

14
Tôi biết! Nhưng những cái tên xấu xí giống như IE6 và có lẽ sẽ không bao giờ biến mất :(
Jim Schubert

5
@JimSchubert, một lần nữa, tôi chỉ sao chép tên loại từ câu hỏi ban đầu để không nhầm lẫn vấn đề. Các nguyên tắc đặt tên NET Enumeration Loại chỉ ra rằng tất cả [Flags]enums nên có tên pluralised, vì vậy tên FlagsEnumthậm chí còn vấn đề nghiêm trọng hơn sự xấu xa.
Drew Noakes

1
Tôi cũng đề xuất Nguyên tắc thiết kế khung: Các quy ước, thành ngữ và mẫu cho các thư viện .NET có thể tái sử dụng . Nó hơi đắt để mua, nhưng tôi tin rằng Safari Online và Books24x7 đều cung cấp nó cho người đăng ký.
Jim Schubert

89

Thành ngữ là sử dụng toán tử bitwise hoặc bằng để đặt bit:

flags |= 0x04;

Để xóa một chút, thành ngữ là sử dụng bitwise và với phủ định:

flags &= ~0x04;

Đôi khi bạn có một phần bù xác định bit của bạn, và sau đó thành ngữ là sử dụng các giá trị này kết hợp với dịch chuyển trái:

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

22

@Đã vẽ

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 lại, 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 tiêu chuẩn bitwise.


10
Trong khi chắc chắn thú vị và tốt để chỉ ra. Bạn cần phải xem xét việc sử dụng. Theo điều này nếu bạn không thực hiện vài trăm nghìn hoặc nhiều hơn thì có lẽ bạn sẽ không nhận thấy điều này.
Joshua Hayes

7
Các HasFlagphương pháp liên quan đến boxing / unboxing, chiếm sự khác biệt này. Nhưng chi phí quá tầm thường (0,4 Đs) đến mức trừ khi bạn ở trong một vòng lặp chặt chẽ, tôi sẽ thực hiện cuộc gọi API khai báo dễ đọc hơn (và ít có lỗi hơn) bất kỳ ngày nào.
Drew Noakes

8
Tùy thuộc vào cách sử dụng, nó có thể là một vấn đề. Và vì tôi làm việc với các trình tải khá nhiều, tôi nghĩ rằng thật tốt khi chỉ ra.
Chuck Dee

11

Rất tiếc, các hoạt động cờ enum tích hợp của .NET rất tiếc. Hầu hết thời gian người dùng còn lại với việc tìm ra logic hoạt động bitwise.

Trong .NET 4, phương thức HasFlagđã được thêm vào để Enumgiúp đơn giản hóa mã của người dùng nhưng không may có nhiều vấn đề với nó.

  1. HasFlag không an toàn kiểu vì nó chấp nhận bất kỳ loại đối số giá trị enum nào, không chỉ loại enum đã cho.
  2. HasFlagkhông rõ ràng về việc nó kiểm tra xem giá trị có tất cả hoặc bất kỳ cờ nào được cung cấp bởi đối số giá trị enum hay không. Nhân tiện đây là tất cả.
  3. HasFlag là khá chậm vì nó đòi hỏi quyền anh gây ra sự phân bổ và do đó nhiều bộ sưu tập rác hơn.

Do một phần hỗ trợ hạn chế của .NET cho enum cờ, tôi đã viết thư viện OSS Enums.NET để giải quyết từng vấn đề này và giúp việc xử lý các enum cờ dễ dàng hơn nhiều.

Dưới đây là một số hoạt động mà nó cung cấp cùng với các triển khai tương đương của chúng chỉ sử dụng .NET framework.

Cờ kết hợp

.MẠNG LƯỚI             flags | otherFlags

Enums.NET flags.CombineFlags(otherFlags)


Xóa cờ

.MẠNG LƯỚI             flags & ~otherFlags

Enums.NET flags.RemoveFlags(otherFlags)


Cờ chung

.MẠNG LƯỚI             flags & otherFlags

Enums.NET flags.CommonFlags(otherFlags)


Chuyển đổi cờ

.MẠNG LƯỚI             flags ^ otherFlags

Enums.NET flags.ToggleFlags(otherFlags)


Có tất cả cờ

.NET             (flags & otherFlags) == otherFlags hoặcflags.HasFlag(otherFlags)

Enums.NET flags.HasAllFlags(otherFlags)


Có cờ nào không

.MẠNG LƯỚI             (flags & otherFlags) != 0

Enums.NET flags.HasAnyFlags(otherFlags)


Nhận cờ

.MẠNG LƯỚI

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()


Tôi đang cố gắng để có những cải tiến này được tích hợp vào .NET Core và cuối cùng có thể là .NET Framework đầy đủ. Bạn có thể kiểm tra đề xuất của tôi ở đây .


7

Cú pháp C ++, giả sử bit 0 là LSB, giả sử các cờ không dấu dài:

Kiểm tra nếu Đặt:

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

Kiểm tra nếu không được đặt:

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

Bộ:

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

Thông thoáng:

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

Chuyển đổi:

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

3

Để có hiệu suất tốt nhất và không rác, hãy sử dụng:

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

Để kiểm tra một chút, bạn sẽ làm như sau: (giả sử cờ là số 32 bit)

Kiểm tra bit:

if((flags & 0x08) == 0x08)
(Nếu bit 4 được đặt thì đúng) Chuyển đổi lại (1 - 0 hoặc 0 - 1):
flags = flags ^ 0x08;
Đặt lại Bit 4 về 0:
flags = flags & 0xFFFFFF7F;


2
-1 vì điều này thậm chí không bận tâm với enums? Thêm vào đó, mã hóa bằng tay các giá trị rất mong manh ... ít nhất tôi sẽ viết ~0x08thay vì 0xFFFFFFF7... (mặt nạ thực tế cho 0x8)
Ben Mosher

1
Lúc đầu, tôi đã nghĩ rằng -1 của Ben rất khắc nghiệt, nhưng việc sử dụng "0xFFFFFF7F" làm cho điều này trở thành một ví dụ đặc biệt tồi.
ToolmakerSteve

2

Điều này được lấy cảm hứng bằng cách sử dụng Bộ làm chỉ mục trong Delphi, quay trở lại khi:

/// 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
Điều này thậm chí không biên dịch nếu BindingFlags là byte enum: this.flags & = ~ index;
đặc biệt

0

Các hoạt động của C ++ là: & | ^ ~ (cho và, hoặc, xor và không hoạt động bitwise). Cũng đáng quan tâm là >> và <<, đó là các hoạt động bithift.

Vì vậy, để kiểm tra một bit được đặt trong cờ, bạn sẽ sử dụng: if (flags & 8) // tests bit 4 đã được đặt


8
Câu hỏi liên quan đến c #, không phải c ++
Andy Johnson

3
Mặt khác, C # sử dụng cùng một toán tử: msdn.microsoft.com/en-us/l Library / 6a71f45d.aspx
ToolmakerSteve

3
Để bảo vệ @ workmad3, các thẻ ban đầu có C và C ++
pqsk
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.