Câu hỏi Thuộc tính Enum [Flags] có nghĩa là gì trong C #?


Theo thời gian, 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 [Flags]thuộc tính.

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


1126
2017-08-12 04:09


gốc


Nó cũng đáng chú ý, ngoài câu trả lời được chấp nhận, rằng VB.NET thực sự đòi hỏi [Flags] - ít nhất là theo các chàng trai .NET: social.msdn.microsoft.com/forums/en-US/csharplanguage/thread/… - Rushyo
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 hợp lý OR, WITHIN chính Enum. Rất tuyệt. Cat = 1, Dog = 2, CatAndDog = Cat || Chó. - Chalky
@Chalky Bạn có nghĩa là CatAndDog = Cat | Dog (OR hợp lý thay vì điều kiện), tôi giả sử? - DdW
@DdW, một phần chính xác: | nên được sử dụng, nhưng | được gọi là OR nhị phân. II là hợp lý HOẶC (cho phép đoản mạch): Ít nhất là theo Microsoft;) msdn.microsoft.com/en-us/library/f355wky8.aspx - Pieter21


Các câu trả lời:


Thuộc tính flags nên được sử dụng bất cứ khi nào enumerable đại diện cho một tập hợp các cờ, 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 chế tác bằng cách sử dụng các toán tử bitwise, ví dụ:

myProperties.AllowedColors = MyColor.Red | MyColor.Green | MyColor.Blue;

Lưu ý rằng [Flags] tự nó không thay đổi điều này ở tất cả - tất cả những gì nó làm là cho phép một đại diện tốt đẹp bởi .ToString() phương pháp:

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"

Điều quan trọng cần lưu ý là [Flags]  không làm tự động tạo ra các giá trị enum lũy thừa 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 bitwise, bởi vì theo mặc định các giá trị bắt đầu bằng 0 và gia tăng.

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

[Flags]
public enum MyColors
{
    Yellow,
    Green,
    Red,
    Blue
}

Các giá trị, nếu khai báo theo cách này, sẽ là Yellow = 0, Green = 1, Red = 2, Blue = 3. Điều này sẽ làm cho nó vô ích khi sử dụng làm cờ.

Dưới đây là ví dụ về tuyên bố chính xác:

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

Để truy xuất các giá trị khác biệt trong thuộc tính của bạn, người ta có thể làm điều này:

if((myProperties.AllowedColors & MyColor.Yellow) == MyColor.Yellow)
{
    // Yellow has been set...
}

if((myProperties.AllowedColors & MyColor.Green) == MyColor.Green)
{
    // Green has been set...
}    

hoặc, trong .NET 4 trở lên:

if (myProperties.AllowedColors.HasFlag(MyColor.Yellow))
{
    // Yellow has been set...
}

Dưới bìa

Điều này hoạt động bởi vì trước đây bạn đã sử dụng quyền hạn của hai trong liệt kê của bạn. Dưới các trang bìa, các giá trị liệt kê của bạn trông như thế này (được trình bày dưới dạng byte, có 8 bit có thể là 1 hoặc 0)

 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 AllowedColors thành Đỏ, Xanh lục và Xanh dương (giá trị nơi OR'ed theo đường ống |), AllowedColors trông như thế này

myProperties.AllowedColors: 00001110

Vì vậy, khi bạn lấy giá trị, bạn thực sự bitwise AND'ing các giá trị

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

Giá trị None = 0

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

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

Sử dụng None làm tên của cờ được liệt kê hằng số có giá trị bằng 0. Bạn không thể sử dụng hằng số Không liệt kê trong một thao tác bit và AND để 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 một phép so sánh logic, không phải là một bitwise giữa giá trị số và hằng số Không liệt kê để 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 flags và cách sử dụng của nó tại msdn và thiết kế cờ tại msdn


1758
2017-08-12 05:10



Cờ chính nó không làm gì cả. Ngoài ra, C # không yêu cầu cờ cho mỗi lần. Nhưng ToString thực hiện enum của bạn sử dụng cờ, và như vậy không Enum.IsDefined, Enum.Parse, vv Cố gắng loại bỏ cờ và xem kết quả của MyColor.Yellow | MyColor.Red; mà 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 khuôn khổ cũng sử dụng [Flags] (ví dụ: XML Serialization). - Ruben
// Màu vàng đã được thiết lập ..., tôi thấy một chút sai lầm. Không có gì đã được thiết lập, nó có nghĩa là màu vàng là một thành viên của AllowedColors của bạn, có lẽ tốt hơn sẽ là / vàng được phép? - sweaver2112
Tôi thích sử dụng hằng số của mẫu A = 1 << 0, B = 1 << 1, C = 1 << 2 ... Dễ đọc, hiểu, kiểm tra và thay đổi trực quan hơn. - Nick Westgate
@ borrrden, yeahh! Tôi đã tìm thấy cái này: msdn.microsoft.com/en-us/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 Khuôn khổ .NET." và "Kiểu 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: public enum bla bla bla - đây là một loại giá trị. Nhưng, phương pháp HasFlag muốn bạn cho anh ta một thể hiện của System.Enum là một lớp (kiểu tham chiếu :) - Aleksei Chepovoi
Enum.IsDefined không tính đến FlagsAttribute. Không có sự trả về nào đúng, ngay cả với thuộc tính: Yellow | Xanh lục, "Vàng, Xanh lục", 3 - TheXenocide


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 sự dịch chuyển bit dễ hơn gõ 4,8,16,32 và cứ thế. Nó không có tác động đến mã của bạn bởi vì tất cả được thực hiện tại thời gian biên dịch


670
2017-08-12 04:37



Và nếu bạn thích sự nhất quán, bạn có thể sử dụng First = 1 << 0. - Roman Starkov
Đó là những gì tôi có nghĩa là, tôi thích có int đầy đủ trong mã nguồn. Nếu tôi có một cột trên một bảng trong cơ sở dữ liệu được gọi là MyEnum lưu trữ giá trị của một trong các enums, và một bản ghi có 131,072, tôi sẽ cần phải nhận ra máy tính của tôi để tìm ra rằng tương ứng với enum với giá trị 1 << 17. Trái ngược với việc chỉ thấy giá trị 131.072 được viết trong nguồn. - JeremyWeir
@JeremyWeir - Một số bit sẽ được đặt trong một giá trị đếm 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 để biểu diễn giá trị số nguyên của bạn theo dạng nhị phân. 131.072 [D] = 0000 0000 0000 0001 0000 0000 0000 0000 [B] [32] .. Với bộ bit thứ 17, giá trị enum được gán 1 << 17 có thể dễ dàng xác định được. 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 ở tất cả mà không nhận được đại diện nhị phân. - Brett Caswell
Cũng chỉ ra rằng bạn có thể thay đổi bit từ các giá trị enum "trước" hơn là trực tiếp từ các số, tức là Third = Second << 1 thay vì Third = 1 << 2 -- xem mô tả đầy đủ hơn bên dưới - drzaus
Tôi thích điều này, nhưng một khi bạn đạt đến 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 về các thay đổi bit như 1 << 31 == -2147483648, 1 << 32 == 1, 1 << 33 == 2, v.v. Ngược lại, nếu bạn nói ThirtySecond = 2147483648 cho một kiểu int enum, trình biên dịch đưa ra một lỗi. - aaaantoine


Kết hợp câu trả lời https://stackoverflow.com/a/8462/1037948 (khai báo thông qua bit-shifting) và https://stackoverflow.com/a/9117/1037948 (bằng cách sử dụng các kết hợp trong khai báo), bạn có thể bit-shift 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 rằng 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ể khai báo

[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

87
2018-05-16 15:03



Các kết hợp là một khuyến nghị tốt, nhưng tôi nghĩ rằng sự thay đổi bit chuỗi sẽ dễ bị sao chép và dán các lỗi như Two = One << 1, Three = One << 1, vv ... 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
@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 rằng bạn có thể - drzaus
+1 cho các kết hợp, tuyệt vời để đại diện cho các trường bitwise một cách rõ ràng. - Forest Kunecke


Vui lòng xem phần sau để biết ví dụ hiển thị khai báo và mức 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");
            }
        }
    }
}

44
2017-08-12 04:32





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 một phương pháp mở rộng để enums để kiểm tra 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 thực hiện:

[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.


30
2017-08-12 18:40



IsSet là một phương pháp mở rộng tôi giả định? - Robert MacLean
Yeah - đọc câu hỏi khác mà tôi liên kết đến để biết chi tiết: stackoverflow.com/questions/7244 - Keith
.NET 4 thêm một HasFlag phương pháp liệt kê, vì vậy bạn có thể làm opt.HasFlag( PossibleOptions.OptionOne ) mà không cần phải viết phần mở rộng của riêng bạn - Orion Edwards


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

19
2017-08-12 15:37





Trong phần mở rộng của câu trả lời được chấp nhận, trong C # 7 các cờ enum có thể được viết bằng cách sử dụng các 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 như thế nào các cờ làm việc dưới bìa.


16
2017-07-20 11:09





Thêm Mode.Write:

Mode = Mode | Mode.Write;

15
2017-08-12 05:59



hoặc Mode | = Mode.Write - abatishchev


Có điều gì đó quá chi tiết với tôi về if ((x & y) == y)... xây dựng, đặc biệt nếu x VÀ y là cả hai bộ hợp chất cờ và bạn chỉ muốn biết nếu có bất kì chồng lên nhau.

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ó giá trị khác 0 [1] sau khi bạn đã bitmap.

[1] Xem nhận xét của Jaime. Nếu chúng tôi thực sự bitmasking, Thứ Tư   chỉ cần kiểm tra kết quả là dương tính. Nhưng kể từ khi enumS   có thể âm, 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 != 0 thay vì > 0.

Đang tắt 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();
        }
    }
}

13
2018-05-01 23:57



Enum có thể dựa trên một loại đã ký, vì vậy bạn nên sử dụng "! = 0" thay vì "> 0". - raven
@JaimePardos - Miễn là chúng ta giữ chúng thành các byte trung thực, như tôi làm trong ví dụ này, không có khái niệm tiêu cực. Chỉ từ 0 đến 255. MSDN cảnh báo, "Hãy thận trọng nếu bạn xác định số âm là một hằng số được liệt kê bởi vì ... [điều đó] có thể làm cho mã của bạn khó hiểu và khuyến khích các 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 nói đúng, nếu chúng ta sử dụng các giá trị tiêu cực trong enum, chúng tôi cần kiểm tra != 0. - ruffin