Thuộc tính [Cờ] Enum có nghĩa là gì trong C #?


1447

Thỉnh thoảng tôi thấy một enum như sau:

[Flags]
public enum Options 
{
    None    = 0,
    Option1 = 1,
    Option2 = 2,
    Option3 = 4,
    Option4 = 8
}

Tôi không hiểu chính xác những gì [Flags]thuộc tính làm.

Bất cứ ai có một lời giải thích tốt hoặc ví dụ họ có thể đăng?


Cũng đáng chú ý, ngoài câu trả lời được chấp nhận, VB.NET thực sự yêu cầu [Cờ] - ít nhất là theo những người .NET: social.msdn.microsoft.com/forums/en-US/csharpl Language / thread / thép
Rushyo

8
Lưu ý, không bắt buộc trong VB những ngày này. Lưu hành vi dưới dạng C # - chỉ thay đổi đầu ra ToString (). Lưu ý, bạn cũng có thể thực hiện logic HOẶC, trong chính Enum. Rất tuyệt. Cát = 1, Chó = 2, CatAndDog = Mèo | | Chó.
Phấn

14
@Chalky Ý bạn là CatAndDog = Cat | Dog(logic HOẶC thay vì Điều kiện), tôi giả sử?
DdW

5
@DdW, đúng một phần: | nên được sử dụng, nhưng | được gọi là nhị phân OR. II là logic HOẶC (cho phép đoản mạch): Ít nhất là theo Microsoft;) msdn.microsoft.com/en-us/l
Library / f355wky8.aspx

Câu trả lời:


2154

Các [Flags]thuộc tính nên được sử dụng bất cứ khi nào đếm được đại diện cho một tập hợp các giá trị có thể, chứ không phải là một giá trị duy nhất. Các bộ sưu tập như vậy thường được sử dụng với các toán tử bitwise, ví dụ:

var allowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Lưu ý rằng [Flags]thuộc tính không tự kích hoạt tính năng này - tất cả những gì nó làm là cho phép biểu diễn đẹp theo .ToString()phương thức:

enum Suits { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }
[Flags] enum SuitsFlags { Spades = 1, Clubs = 2, Diamonds = 4, Hearts = 8 }

...

var str1 = (Suits.Spades | Suits.Diamonds).ToString();
           // "5"
var str2 = (SuitsFlags.Spades | SuitsFlags.Diamonds).ToString();
           // "Spades, Diamonds"

Cũng cần lưu ý rằng [Flags] không tự động tạo ra sức mạnh giá trị enum của hai. Nếu bạn bỏ qua các giá trị số, enum sẽ không hoạt động như người ta có thể mong đợi trong các hoạt động theo bit, bởi vì theo mặc định, các giá trị bắt đầu bằng 0 và tăng dần.

Khai báo không chính xác:

[Flags]
public enum MyColors
{
    Yellow,  // 0
    Green,   // 1
    Red,     // 2
    Blue     // 3
}

Các giá trị, nếu được khai báo theo cách này, sẽ là Vàng = 0, Xanh = 1, Đỏ = 2, Xanh = 3. Điều này sẽ khiến nó vô dụng dưới dạng cờ.

Đây là một ví dụ về một tuyên bố chính xác:

[Flags]
public enum MyColors
{
    Yellow = 1,
    Green = 2,
    Red = 4,
    Blue = 8
}

Để lấy các giá trị riêng biệt trong tài sản của bạn, người ta có thể làm điều này:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow is allowed...
}

hoặc trước .NET 4:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow is allowed...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green is allowed...
}    

Dưới vỏ bọc

Điều này hoạt động bởi vì bạn đã sử dụng quyền hạn của hai trong bảng liệt kê của bạn. Dưới vỏ bọc, các giá trị liệt kê của bạn trông như thế này trong các số nhị phân và số không:

 Yellow: 00000001
 Green:  00000010
 Red:    00000100
 Blue:   00001000

Tương tự như vậy, sau khi bạn đã đặt thuộc tính của mình làCoolors thành màu đỏ, xanh lục và xanh lam bằng cách sử dụng toán |tử nhị phân OR theo bit , cho phépCoolors trông như sau:

myProperties.AllowedColors: 00001110

Vì vậy, khi bạn truy xuất giá trị, bạn thực sự đang thực hiện bitwise VÀ &trên các giá trị:

myProperties.AllowedColors: 00001110
             MyColor.Green: 00000010
             -----------------------
                            00000010 // Hey, this is the same as MyColor.Green!

Giá trị Không = 0

Và liên quan đến việc sử dụng 0trong bảng liệt kê của bạn, trích dẫn từ MSDN:

[Flags]
public enum MyColors
{
    None = 0,
    ....
}

Sử dụng Không làm tên của hằng số liệt kê cờ có giá trị bằng không. Bạn không thể sử dụng hằng số liệt kê Không có trong thao tác bitwise để kiểm tra cờ vì kết quả luôn bằng không. Tuy nhiên, bạn có thể thực hiện so sánh logic, không theo bit, giữa giá trị số và hằng số liệt kê Không để xác định xem có bất kỳ bit nào trong giá trị số được đặt hay không.

Bạn có thể tìm thêm thông tin về thuộc tính cờ và cách sử dụng tại msdnthiết kế cờ tại msdn


156
Cờ tự nó không làm gì cả. Ngoài ra, C # không yêu cầu Cờ mỗi lần. Tuy nhiên, ToStringviệc thực hiện các enum của bạn sử dụng Flags, và vì vậy không Enum.IsDefined, Enum.Parsevv Hãy cố gắng loại bỏ Flags và nhìn vào kết quả của MyColor.Yellow | MyColor.Red; không có nó, bạn nhận được "5", với Cờ bạn nhận được "Vàng, Đỏ". Một số phần khác của khung cũng sử dụng [Cờ] (ví dụ: Tuần tự hóa XML).
Ruben

7
// Màu vàng đã được đặt ..., tôi thấy hơi sai lệch. Không có gì được thiết lập, điều đó có nghĩa là Màu vàng là thành viên của Được phép của bạn, có lẽ tốt hơn là // Màu vàng được cho phép?
Scott Weaver

48
Tôi thích sử dụng các hằng số có dạng A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Dễ đọc hơn, hiểu, kiểm tra và thay đổi trực quan.
Nick Westgate

2
@borrrden, chết tiệt! Tôi đã tìm thấy điều này: msdn.microsoft.com/en-us/l Library / system.enum.aspx - xem phần "Ghi chú": "Enum là lớp cơ sở cho tất cả các liệt kê trong .NET Framework." và "Bảng liệt kê không kế thừa rõ ràng từ Enum; mối quan hệ thừa kế được xử lý ngầm bởi trình biên dịch." Vì vậy, khi bạn viết: công khai enum bla bla bla - đây là một loại giá trị. Tuy nhiên, phương thức HasFlag muốn bạn đưa cho anh ta một thể hiện của System.Enum là một lớp (kiểu tham chiếu :)
Aleksei Chepovoi

2
Nếu bạn muốn loại trừ một cờ khỏi bảng liệt kê, hãy sử dụng xor, đó là ^ trong C #. Vì vậy, nếu bạn có myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;. Bạn có thể làm:myProperties.AllowedColors = myProperties.AllowedColors ^ MyColor.Blue //myProperties.AllowedColors == MyColor.Red | Mycolor.Green
Josh Noe

779

Bạn cũng có thể làm điều này

[Flags]
public enum MyEnum
{
    None   = 0,
    First  = 1 << 0,
    Second = 1 << 1,
    Third  = 1 << 2,
    Fourth = 1 << 3
}

Tôi thấy việc dịch chuyển bit dễ dàng hơn so với việc gõ 4,8,16,32, v.v. Nó không ảnh hưởng đến mã của bạn vì tất cả đều được thực hiện vào thời gian biên dịch


18
Ý tôi là, tôi thích có mã nguồn đầy đủ. Nếu tôi có một cột trên một bảng trong cơ sở dữ liệu có tên MyEnum lưu trữ giá trị của một trong số các enum và một bản ghi có 131.072, tôi sẽ cần phải lấy ra máy tính của mình để tìm ra giá trị tương ứng với enum với giá trị 1 << 17. Trái ngược với việc chỉ nhìn thấy giá trị 131.072 được ghi trong nguồn.
Jeremy Weir

6
@jwg Tôi đồng ý, thật ngớ ngẩn khi quá lo lắng về hiệu năng thời gian chạy của việc này, nhưng tất cả đều giống nhau, tôi nghĩ thật tuyệt khi biết rằng điều này sẽ không chèn bithifts bất cứ nơi nào bạn sử dụng enum. Nhiều thứ 'gọn gàng' hơn là bất cứ thứ gì liên quan đến hiệu suất
Orion Edwards

18
@Jeremy Weir - Một số bit sẽ được đặt trong giá trị liệt kê cờ. Vì vậy, phương pháp phân tích dữ liệu của bạn là những gì không đúng. Chạy một thủ tục để thể hiện giá trị số nguyên của bạn trong nhị phân. 131,072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Với tập bit thứ 17, giá trị enum được gán 1 << 17 có thể xác định dễ dàng. 0110 0011 0011 0101 0101 0011 0101 1011 [b] [32] .. các giá trị enum gán 1 << 31, 1 << 30, 1 << 26, 1 << 25 .. v.v ... có thể xác định được mà không cần viện trợ của một máy tính ở tất cả .. mà tôi nghi ngờ bạn sẽ có thể xác định được tất cả mà không cần đại diện nhị phân.
Brett Caswell

13
Đồng thời chỉ ra rằng bạn có thể dịch chuyển bit từ các giá trị enum "trước đó" thay vì trực tiếp từ các số, Third = Second << 1thay vì Third = 1 << 2- xem mô tả đầy đủ hơn bên dưới
drzaus

26
Tôi như thế này, nhưng một khi bạn nhận được để các giới hạn trên của kiểu enum cơ bản, trình biên dịch không cảnh báo chống lại sự thay đổi chút như 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2, và vân vân. Ngược lại, nếu bạn nói ThirtySecond = 2147483648cho một kiểu int enum, trình biên dịch sẽ đưa ra một lỗi.
aaaantoine

115

Kết hợp các câu trả lời https://stackoverflow.com/a/8462/1037948 (khai báo qua dịch chuyển bit) và https://stackoverflow.com/a/9117/1037948 (sử dụng kết hợp trong khai báo), bạn có thể thay đổi các giá trị trước đó hơn là sử dụng số. Không nhất thiết phải giới thiệu nó, nhưng chỉ cần chỉ ra bạn có thể.

Thay vì:

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,   // 1
    Two     = 1 << 1,   // 2
    Three   = 1 << 2,   // 4
    Four    = 1 << 3,   // 8

    // combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Bạn có thể tuyên bố

[Flags]
public enum Options : byte
{
    None    = 0,
    One     = 1 << 0,       // 1
    // now that value 1 is available, start shifting from there
    Two     = One << 1,     // 2
    Three   = Two << 1,     // 4
    Four    = Three << 1,   // 8

    // same combinations
    OneAndTwo = One | Two,
    OneTwoAndThree = One | Two | Three,
}

Xác nhận với LinqPad:

foreach(var e in Enum.GetValues(typeof(Options))) {
    string.Format("{0} = {1}", e.ToString(), (byte)e).Dump();
}

Kết quả trong:

None = 0
One = 1
Two = 2
OneAndTwo = 3
Three = 4
OneTwoAndThree = 7
Four = 8

23
Các kết hợp là một đề xuất tốt, nhưng tôi nghĩ rằng dịch chuyển bit bị xiềng xích sẽ dễ bị lỗi sao chép và dán như Two = One << 1, Three = One << 1, v.v ... Các số nguyên tăng dần của mẫu 1 << n an toàn hơn và ý định rõ ràng hơn.
Rupert Rawnsley

2
@RupertRawnsley để trích dẫn câu trả lời của tôi:> Không nhất thiết phải giới thiệu nó, nhưng chỉ cần chỉ ra bạn có thể
drzaus

49

Vui lòng xem phần sau đây để biết ví dụ cho thấy khai báo và cách sử dụng tiềm năng:

namespace Flags
{
    class Program
    {
        [Flags]
        public enum MyFlags : short
        {
            Foo = 0x1,
            Bar = 0x2,
            Baz = 0x4
        }

        static void Main(string[] args)
        {
            MyFlags fooBar = MyFlags.Foo | MyFlags.Bar;

            if ((fooBar & MyFlags.Foo) == MyFlags.Foo)
            {
                Console.WriteLine("Item has Foo flag set");
            }
        }
    }
}

Ví dụ này hoạt động ngay cả khi bạn rời khỏi [Cờ]. Đó là phần [Cờ] tôi đang cố gắng tìm hiểu.
gbarry

39

Để mở rộng câu trả lời được chấp nhận, trong C # 7, cờ enum có thể được viết bằng chữ nhị phân:

[Flags]
public enum MyColors
{
    None   = 0b0000,
    Yellow = 0b0001,
    Green  = 0b0010,
    Red    = 0b0100,
    Blue   = 0b1000
}

Tôi nghĩ rằng đại diện này làm cho nó rõ ràng làm thế nào các cờ hoạt động dưới vỏ bọc .


2
Và trong C # 7.2, nó thậm chí còn rõ ràng hơn với dải phân cách hàng đầu ! 0b_0100
Vincenzo Petronio

37

Tôi hỏi gần đây về một cái gì đó tương tự.

Nếu bạn sử dụng cờ, bạn có thể thêm phương thức mở rộng vào enum để giúp kiểm tra các cờ được chứa dễ dàng hơn (xem bài để biết chi tiết)

Điều này cho phép bạn làm:

[Flags]
public enum PossibleOptions : byte
{
    None = 0,
    OptionOne = 1,
    OptionTwo = 2,
    OptionThree = 4,
    OptionFour = 8,

    //combinations can be in the enum too
    OptionOneAndTwo = OptionOne | OptionTwo,
    OptionOneTwoAndThree = OptionOne | OptionTwo | OptionThree,
    ...
}

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

PossibleOptions opt = PossibleOptions.OptionOneTwoAndThree 

if( opt.IsSet( PossibleOptions.OptionOne ) ) {
    //optionOne is one of those set
}

Tôi thấy điều này dễ đọc hơn hầu hết các cách kiểm tra các cờ được bao gồm.


Is set là một phương thức mở rộng tôi giả sử?
Robert MacLean

Vâng - đọc câu hỏi khác mà tôi liên kết để biết chi tiết: stackoverflow.com/questions/7244
Keith

71
.NET 4 thêm một HasFlagphương thức vào bảng liệt kê, vì vậy bạn có thể thực hiện opt.HasFlag( PossibleOptions.OptionOne )mà không cần phải viết các tiện ích mở rộng của riêng mình
Orion Edwards

1
Lưu ý rằng HasFlagchậm hơn nhiều so với làm hoạt động trên bit.
Wai Ha Lee

1
@WaiHaLee Bạn có thể sử dụng phương pháp CodeJam.EnumHelper.IsFlagSet mở rộng được biên dịch sang một phiên bản nhanh chóng sử dụng Expression: github.com/rsdn/CodeJam/wiki/M_CodeJam_EnumHelper_IsFlagSet__1
NN_

22

@Nidonocu

Để thêm một cờ khác vào một tập hợp các giá trị hiện có, hãy sử dụng toán tử gán OR.

Mode = Mode.Read;
//Add Mode.Write
Mode |= Mode.Write;
Assert.True(((Mode & Mode.Write) == Mode.Write)
  && ((Mode & Mode.Read) == Mode.Read)));

18

Khi làm việc với cờ tôi thường khai báo thêm Không và Tất cả các mục. Đây là hữu ích để kiểm tra xem tất cả các cờ được đặt hay không có cờ nào được đặt.

[Flags] 
enum SuitsFlags { 

    None =     0,

    Spades =   1 << 0, 
    Clubs =    1 << 1, 
    Diamonds = 1 << 2, 
    Hearts =   1 << 3,

    All =      ~(~0 << 4)

}

Sử dụng:

Spades | Clubs | Diamonds | Hearts == All  // true
Spades & Clubs == None  // true

 
Cập nhật 2019-10:

Vì C # 7.0, bạn có thể sử dụng các chữ nhị phân, có thể trực quan hơn để đọc:

[Flags] 
enum SuitsFlags { 

    None =     0b0000,

    Spades =   0b0001, 
    Clubs =    0b0010, 
    Diamonds = 0b0100, 
    Hearts =   0b1000,

    All =      0b1111

}


14

Có điều gì đó quá dài dòng đối với tôi về if ((x & y) == y)...cấu trúc, đặc biệt nếu xAND ylà cả hai bộ cờ ghép và bạn chỉ muốn biết nếu có bất kỳ sự chồng chéo .

Trong trường hợp này, tất cả những gì bạn thực sự cần biết là nếu có một giá trị khác không [1] sau khi bạn bitmasked .

[1] Xem bình luận của Jaime. Nếu chúng tôi xác thực bitmasking , chúng tôi chỉ cần kiểm tra xem kết quả có khả quan không. Nhưng kể từ enums có thể là tiêu cực, thậm chí, kỳ lạ, khi kết hợp với các [Flags] thuộc tính , đó là phòng thủ để mã cho != 0hơn > 0.

Xây dựng thiết lập của @ andnil ...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BitFlagPlay
{
    class Program
    {
        [Flags]
        public enum MyColor
        {
            Yellow = 0x01,
            Green = 0x02,
            Red = 0x04,
            Blue = 0x08
        }

        static void Main(string[] args)
        {
            var myColor = MyColor.Yellow | MyColor.Blue;
            var acceptableColors = MyColor.Yellow | MyColor.Red;

            Console.WriteLine((myColor & MyColor.Blue) != 0);     // True
            Console.WriteLine((myColor & MyColor.Red) != 0);      // False                
            Console.WriteLine((myColor & acceptableColors) != 0); // True
            // ... though only Yellow is shared.

            Console.WriteLine((myColor & MyColor.Green) != 0);    // Wait a minute... ;^D

            Console.Read();
        }
    }
}

Enum có thể dựa trên loại đã ký, vì vậy bạn nên sử dụng "! = 0" thay vì "> 0".
quạ

@JaimePardos - Miễn là chúng tôi giữ cho chúng các byte trung thực, như tôi làm trong ví dụ này, không có khái niệm về phủ định. Chỉ cần 0 đến 255. Như MSDN cảnh báo , "Hãy thận trọng nếu bạn xác định số âm là hằng số liệt kê cờ vì ... [điều đó] có thể làm cho mã của bạn bị nhầm lẫn và khuyến khích lỗi mã hóa." Thật kỳ lạ khi nghĩ về "bitflags tiêu cực"! ; ^) Tôi sẽ chỉnh sửa thêm một chút. Nhưng bạn đã đúng, nếu chúng tôi sử dụng các giá trị âm trong chúng tôi enum, chúng tôi cần kiểm tra != 0.
ruffin

10

Cờ cho phép bạn sử dụng bitmasking bên trong bảng liệt kê của bạn. Điều này cho phép bạn kết hợp các giá trị liệt kê, trong khi vẫn giữ lại các giá trị được chỉ định.

[Flags]
public enum DashboardItemPresentationProperties : long
{
    None = 0,
    HideCollapse = 1,
    HideDelete = 2,
    HideEdit = 4,
    HideOpenInNewWindow = 8,
    HideResetSource = 16,
    HideMenu = 32
}

0

Xin lỗi nếu ai đó đã nhận thấy kịch bản này. Một ví dụ hoàn hảo về cờ chúng ta có thể thấy trong sự phản chiếu. Có Binding Flag ENUM. https://docs.microsoft.com/en-us/dotnet/api/system.reflection.bindingflags?view=netframework-4.8

[System.Flags]
[System.Runtime.InteropServices.ComVisible(true)]
[System.Serializable]
public enum BindingFlags

Sử dụng

             // BindingFlags.InvokeMethod
            // Call a static method.
            Type t = typeof (TestClass);

            Console.WriteLine();
            Console.WriteLine("Invoking a static method.");
            Console.WriteLine("-------------------------");
            t.InvokeMember ("SayHello", BindingFlags.InvokeMethod | BindingFlags.Public | 
                BindingFlags.Static, null, null, new object [] {});
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.