Làm thế nào để lặp lại các giá trị của một Enum có cờ?


131

Nếu tôi có một biến giữ cờ enum, bằng cách nào đó tôi có thể lặp lại các giá trị bit trong biến cụ thể đó không? Hoặc tôi phải sử dụng Enum.GetValues ​​để lặp lại toàn bộ enum và kiểm tra cái nào được đặt?


Nếu bạn có quyền kiểm soát API của mình, hãy tránh sử dụng cờ bit. Chúng hiếm khi là một tối ưu hóa hữu ích. Sử dụng một cấu trúc với một số trường 'bool' công khai là tương đương về mặt ngữ nghĩa nhưng mã của bạn đơn giản hơn đáng kể. Và nếu bạn cần sau này, bạn có thể thay đổi các trường thành các thuộc tính thao túng các trường bit bên trong, đóng gói tối ưu hóa.
Jay Bazuzi

2
Tôi thấy những gì bạn đang nói, và trong nhiều trường hợp nó sẽ có ý nghĩa, nhưng trong trường hợp này, tôi sẽ gặp vấn đề tương tự như đề xuất If ... Tôi phải viết các câu lệnh If cho hàng tá bool khác nhau sử dụng một vòng lặp foreach đơn giản trên một mảng. (Và vì đây là một phần của DLL công khai, tôi không thể thực hiện những việc phức tạp như chỉ cần có một loạt các công cụ hoặc những gì có bạn.)

10
"Mã của bạn đơn giản hơn đáng kể" - hoàn toàn ngược lại nếu bạn đang làm bất cứ điều gì ngoài việc chỉ kiểm tra các bit riêng lẻ ... các vòng lặp và thiết lập các hoạt động trở nên gần như không thể vì các trường và thuộc tính không phải là các thực thể hạng nhất.
Jim Balter

2
@nawfal "một chút" Tôi thấy những gì bạn đã làm ở đó
stannius

Câu trả lời:


179
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}

7
Lưu ý rằng HasFlagcó sẵn từ .NET 4 trở đi.
Andreas Grech

3
Điều đó thật tuyệt! Nhưng bạn có thể làm cho nó thậm chí đơn giản hơn và dễ sử dụng. Chỉ cần dán cái này như một phương thức mở rộng: Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); Sau đó, chỉ cần: myEnum.GetFLags():)
joshcomley

3
Tuyệt vời một lót, josh, nhưng nó vẫn gặp phải vấn đề chọn các giá trị nhiều cờ (Boo) thay vì chỉ các giá trị cờ đơn (Bar, Baz), như trong câu trả lời của Jeff, ở trên.

10
Đẹp - coi chừng Không có ai - ví dụ: Vật phẩm. Từ câu trả lời của Jeff sẽ luôn được đưa vào
Ilan

1
Chữ ký của phương thức phải làstatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers 11/03/2015

48

Đây là một giải pháp Linq cho vấn đề.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}

7
Tại sao điều này không đứng đầu ?? :) Sử dụng .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));nếu bạn có giá trị bằng 0 để đại diệnNone
georgiosd

Siêu sạch. Giải pháp tốt nhất theo ý kiến ​​của tôi.
Ryan Fiorini

@georgiosd Tôi đoán hiệu suất không phải là tuyệt vời. (Nhưng phải đủ tốt cho hầu hết các nhiệm vụ)
AntiHeadshot

41

Không có bất kỳ phương thức dựng sẵn nào để có được từng thành phần theo như tôi biết. Nhưng đây là một cách bạn có thể có được chúng:

[Flags]
enum Items
{
    None = 0x0,
    Foo  = 0x1,
    Bar  = 0x2,
    Baz  = 0x4,
    Boo  = 0x6,
}

var value = Items.Foo | Items.Bar;
var values = value.ToString()
                  .Split(new[] { ", " }, StringSplitOptions.None)
                  .Select(v => (Items)Enum.Parse(typeof(Items), v));

// This method will always end up with the most applicable values
value = Items.Bar | Items.Baz;
values = value.ToString()
              .Split(new[] { ", " }, StringSplitOptions.None)
              .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo

Tôi đã điều chỉnh những gì Enumbên trong để tạo chuỗi thay vì trả về các cờ. Bạn có thể nhìn vào mã trong gương phản xạ và nên tương đương nhiều hơn hoặc ít hơn. Hoạt động tốt cho các trường hợp sử dụng chung trong đó có các giá trị chứa nhiều bit.

static class EnumExtensions
{
    public static IEnumerable<Enum> GetFlags(this Enum value)
    {
        return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
    }

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
    {
        return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
    }

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
    {
        ulong bits = Convert.ToUInt64(value);
        List<Enum> results = new List<Enum>();
        for (int i = values.Length - 1; i >= 0; i--)
        {
            ulong mask = Convert.ToUInt64(values[i]);
            if (i == 0 && mask == 0L)
                break;
            if ((bits & mask) == mask)
            {
                results.Add(values[i]);
                bits -= mask;
            }
        }
        if (bits != 0L)
            return Enumerable.Empty<Enum>();
        if (Convert.ToUInt64(value) != 0L)
            return results.Reverse<Enum>();
        if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
            return values.Take(1);
        return Enumerable.Empty<Enum>();
    }

    private static IEnumerable<Enum> GetFlagValues(Type enumType)
    {
        ulong flag = 0x1;
        foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
        {
            ulong bits = Convert.ToUInt64(value);
            if (bits == 0L)
                //yield return value;
                continue; // skip the zero value
            while (flag < bits) flag <<= 1;
            if (flag == bits)
                yield return value;
        }
    }
}

Phương thức mở rộng GetIndividualFlags()nhận tất cả các cờ riêng lẻ cho một loại. Vì vậy, các giá trị chứa nhiều bit bị bỏ lại.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz

Tôi đã xem xét thực hiện phân tách chuỗi, nhưng điều đó có thể tốn nhiều chi phí hơn là chỉ lặp lại toàn bộ giá trị bit của enum.

Thật không may bằng cách làm như vậy, bạn sẽ phải kiểm tra các giá trị dư thừa (nếu không muốn chúng). Xem ví dụ thứ hai của tôi, nó sẽ mang lại Bar, BazBoothay vì chỉ Boo.
Jeff Mercado

Thật thú vị khi bạn có thể đưa Boo ra khỏi đó, mặc dù phần đó không cần thiết (và trên thực tế, một ý tưởng thực sự tồi tệ :)) cho những gì tôi đang làm.

Điều này có vẻ thú vị, nhưng nếu tôi không hiểu lầm, nó sẽ trả lại Boo cho enum ở trên, nơi tôi chỉ muốn liệt kê các phiên bản không kết hợp các giá trị khác (ví dụ: phiên bản có sức mạnh của hai) . Điều đó có thể được thực hiện dễ dàng? Tôi đã cố gắng và tôi không thể nghĩ ra một cách dễ dàng để xác định điều đó mà không cần dùng đến toán học FP.

@Robin: Bạn nói đúng, bản gốc sẽ trả về Boo(giá trị được trả về bằng cách sử dụng ToString()). Tôi đã điều chỉnh nó để chỉ cho phép các cờ riêng lẻ. Vì vậy, trong ví dụ của tôi, bạn có thể nhận BarBazthay vì Boo.
Jeff Mercado

26

Trở lại điều này một vài năm sau đó, với một chút kinh nghiệm, câu trả lời cuối cùng của tôi chỉ dành cho các giá trị bit đơn, chuyển từ bit thấp nhất sang bit cao nhất, là một biến thể nhỏ của thói quen bên trong của Jeff Mercado:

public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value))
        {
            yield return value;
        }
    }
}

Nó dường như hoạt động, và bất chấp sự phản đối của tôi vài năm trước, tôi sử dụng HasFlag ở đây, vì nó dễ đọc hơn nhiều so với sử dụng so sánh bitwise và sự khác biệt tốc độ là không đáng kể cho bất cứ điều gì tôi sẽ làm. (Hoàn toàn có thể họ đã cải thiện tốc độ của HasFlags kể từ đó, cho tất cả những gì tôi biết ... Tôi chưa thử nghiệm.)


Chỉ cần một cờ ghi chú được khởi tạo thành một int nên là một ulong, chẳng hạn như bit, nên được khởi tạo là 1ul
bắt buộc vào

Cảm ơn, tôi sẽ sửa nó! (Tôi vừa mới đi và kiểm tra mã thực tế của mình và tôi đã sửa nó theo cách khác bằng cách tuyên bố cụ thể nó là một ulong.)

2
Đây là giải pháp duy nhất tôi thấy dường như cũng không bị ảnh hưởng bởi thực tế là nếu bạn có một cờ có giá trị 0, đại diện cho "Không", các câu trả lời khác phương thức GetFlag () sẽ trả về YourEnum. Đây là một trong những cờ ngay cả khi nó không thực sự trong enum bạn đang chạy phương thức trên! Tôi đã nhận được các mục nhật ký trùng lặp kỳ lạ bởi vì các phương thức đã chạy nhiều lần hơn tôi mong đợi khi chúng chỉ có một cờ enum khác không. Cảm ơn đã dành thời gian để cập nhật và thêm vào giải pháp tuyệt vời này!
BrianH

yield return bits;?
Jaider

1
Tôi muốn một biến enum "Tất cả", mà tôi đã gán ulong.MaxValue, vì vậy tất cả các bit được đặt thành '1'. Nhưng mã của bạn đạt một vòng lặp vô hạn, bởi vì cờ <bit không bao giờ đánh giá là đúng (các vòng lặp cờ thành âm và sau đó bị kẹt ở 0).
Haighstrom

15

Đi ra khỏi phương pháp của @ Greg, nhưng thêm một tính năng mới từ C # 7.3, Enumràng buộc:

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
    where T : Enum    // New constraint for C# 7.3
{
    foreach (Enum value in Enum.GetValues(flags.GetType()))
        if (flags.HasFlag(value))
            yield return (T)value;
}

Ràng buộc mới cho phép đây là một phương thức mở rộng mà không cần phải truyền qua (int)(object)evà tôi có thể sử dụng HasFlagphương thức đó và truyền trực tiếp Ttừ đó value.

C # 7.3 cũng đã thêm các ràng buộc cho delagates và unmanaged.


4
Có lẽ bạn cũng muốn flagstham số thuộc loại chung T, nếu không, bạn phải xác định rõ ràng loại enum mỗi khi bạn gọi nó.
Ray

11

+1 cho câu trả lời được cung cấp bởi @ RobinHood70. Tôi thấy rằng một phiên bản chung của phương pháp là thuận tiện cho tôi.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("The generic type parameter must be an Enum.");

    if (flags.GetType() != typeof(T))
        throw new ArgumentException("The generic type parameter does not match the target type.");

    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

EDIT Và +1 cho @AustinWBryan vì đã đưa C # 7.3 vào không gian giải pháp.

public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

3

Bạn không cần phải lặp lại tất cả các giá trị. chỉ cần kiểm tra các cờ cụ thể của bạn như vậy:

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) 
{
   //do something...
}

hoặc (như pstrjds đã nói trong các bình luận) bạn có thể kiểm tra việc sử dụng nó như:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}

5
Nếu bạn đang sử dụng .Net 4.0, có một phương thức mở rộng HasFlag mà bạn có thể sử dụng để làm điều tương tự: myVar.HasFlag (FlagsEnum.Flag1)
pstrjds

1
Nếu một lập trình viên không thể hiểu một chút về thao tác VÀ họ nên đóng gói nó và tìm một nghề nghiệp mới.
Ed S.

2
@Ed: đúng, nhưng HasFlag tốt hơn khi bạn đọc lại mã ... (có thể sau vài tháng hoặc vài năm)
Tiến sĩ TJ

4
@Ed Swangren: Đó thực sự là về việc làm cho mã dễ đọc hơn và ít dài dòng hơn, không nhất thiết vì sử dụng các thao tác bitwise là "khó".
Jeff Mercado

2
HasFlag rất chậm. Hãy thử một vòng lặp lớn bằng HasFlag so với mặt nạ bit và bạn sẽ nhận thấy sự khác biệt rất lớn.

3

Những gì tôi đã làm là thay đổi cách tiếp cận của mình, thay vì nhập tham số đầu vào của phương thức là enumkiểu, tôi đã gõ nó dưới dạng một mảng của enumkiểu ( MyEnum[] myEnums), theo cách này tôi chỉ lặp qua mảng với câu lệnh chuyển đổi bên trong vòng lặp.


2

Không hài lòng với câu trả lời ở trên, mặc dù chúng là khởi đầu.

Sau khi kết hợp một số nguồn khác nhau ở đây:
Áp phích trước trong chủ đề này
Mã dự án Mã QnA của Enum Kiểm tra bài đăng
Great Enum <T> Utility

Tôi đã tạo ra điều này để cho tôi biết những gì bạn nghĩ.
Tham số ::
bool checkZerobảo nó cho phép 0dưới dạng giá trị cờ. Theo mặc định input = 0trả về sản phẩm nào.
bool checkFlags: bảo nó kiểm tra xem Enumtrang trí có [Flags]thuộc tính không.
Tái bút Tôi không có thời gian ngay bây giờ để tìm ra checkCombinators = falsealg sẽ buộc nó bỏ qua mọi giá trị enum là sự kết hợp của các bit.

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true)
    {
        Type enumType = typeof(TEnum);
        if (!enumType.IsEnum)
            yield break;

        ulong setBits = Convert.ToUInt64(input);
        // if no flags are set, return empty
        if (!checkZero && (0 == setBits))
            yield break;

        // if it's not a flag enum, return empty
        if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false))
            yield break;

        if (checkCombinators)
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum<TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }
        else
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum <TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }

    }

Điều này sử dụng Enum Class Enum <T> được tìm thấy ở đây mà tôi đã cập nhật để sử dụng yield returncho GetValues:

public static class Enum<TEnum>
{
    public static TEnum Parse(string value)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public static IEnumerable<TEnum> GetValues()   
    {
        foreach (object value in Enum.GetValues(typeof(TEnum)))
            yield return ((TEnum)value);
    }
}  

Cuối cùng, đây là một ví dụ về việc sử dụng nó:

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

Xin lỗi, đã không có thời gian để xem xét dự án này trong vài ngày. Tôi sẽ liên lạc lại với bạn sau khi tôi đã hiểu rõ hơn về mã của bạn.

4
Chỉ cần có một lỗi trong mã đó. Mã trong cả hai nhánh của câu lệnh if (checkCombinators) là giống hệt nhau. Ngoài ra, có lẽ không phải là một lỗi, nhưng thật bất ngờ, là nếu bạn có giá trị enum được khai báo là 0, nó sẽ luôn được trả về trong bộ sưu tập. Có vẻ như chỉ nên trả lại nếu checkZero là đúng và không có cờ nào khác được đặt.
dhochee

@dhochee. Tôi đồng ý. Hoặc mã khá đẹp, nhưng đối số là khó hiểu.
Thu hút

2

Dựa trên câu trả lời của Greg ở trên, điều này cũng quan tâm đến trường hợp bạn có giá trị 0 trong enum của mình, chẳng hạn như none = 0. Trong trường hợp đó, nó không nên lặp lại giá trị đó.

public static IEnumerable<Enum> ToEnumerable(this Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value) && Convert.ToInt64(value) != 0)
            yield return value;
}

Có ai biết làm thế nào để cải thiện điều này hơn nữa để nó có thể xử lý trường hợp tất cả các cờ trong enum được đặt theo cách siêu thông minh có thể xử lý tất cả các loại enum cơ bản và trường hợp All = ~ 0 và All = EnumValue1 | EnumValue2 | EnumValue3 | ...


1

Bạn có thể sử dụng Iterator từ Enum. Bắt đầu từ mã MSDN:

public class DaysOfTheWeek : System.Collections.IEnumerable
{
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 };
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    public string value { get; set; }

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            if value >> i & 1 == dayflag[i] {
                yield return days[i];
            }
        }
    }
}

Nó không được kiểm tra, vì vậy nếu tôi mắc lỗi, hãy gọi tôi ra. (rõ ràng nó không được đăng ký lại.) Bạn phải gán giá trị trước hoặc chia nó thành một hàm khác sử dụng enum.dayflag và enum.days. Bạn có thể đi đâu đó với đề cương.


0

Nó có thể cũng như mã sau đây:

public static string GetEnumString(MyEnum inEnumValue)
{
    StringBuilder sb = new StringBuilder();

    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum )))
    {
        if ((e & inEnumValue) != 0)
        {
           sb.Append(e.ToString());
           sb.Append(", ");
        }
    }

   return sb.ToString().Trim().TrimEnd(',');
}

Nó đi vào bên trong nếu chỉ khi giá trị enum được chứa trên giá trị


0

Tất cả các câu trả lời hoạt động tốt với các cờ đơn giản, có lẽ bạn sẽ gặp vấn đề khi các cờ được kết hợp.

[Flags]
enum Food
{
  None=0
  Bread=1,
  Pasta=2,
  Apples=4,
  Banana=8,
  WithGluten=Bread|Pasta,
  Fruits = Apples | Banana,
}

có lẽ cần thêm một kiểm tra để kiểm tra xem giá trị enum mà nó tự kết hợp hay không. Bạn có thể cần một cái gì đó như được đăng ở đây bởi Henk van Boeijen để đáp ứng yêu cầu của bạn (bạn cần cuộn xuống một chút)


0

Phương pháp mở rộng bằng cách sử dụng ràng buộc Enum và generic để ngăn truyền:

public static class EnumExtensions
{
    public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
    {
        return Enum
            .GetValues(typeof(T))
            .Cast<T>()
            .Where(e => flagsEnumValue.HasFlag(e))
            .ToArray();
    }
}

-1

Bạn có thể làm điều đó trực tiếp bằng cách chuyển đổi sang int nhưng bạn sẽ mất kiểm tra kiểu. Tôi nghĩ rằng cách tốt nhất là sử dụng một cái gì đó tương tự như đề xuất của tôi. Nó giữ đúng loại tất cả các cách. Không cần chuyển đổi. Nó không hoàn hảo do quyền anh sẽ thêm một chút thành tích.

Không hoàn hảo (quyền anh), nhưng nó thực hiện công việc mà không có cảnh báo ...

/// <summary>
/// Return an enumerators of input flag(s)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static IEnumerable<T> GetFlags<T>(this T input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
    {
        if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0.
        {
            if (((Enum) (object) input).HasFlag(value))
                yield return (T) (object) value;
        }
    }
}

Sử dụng:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }
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.