2012-04-11 39 views
11

Tôi đang cố gắng tạo một phương thức trợ giúp để liệt kê tên của tất cả các bit được đặt trong một giá trị Enum (cho mục đích ghi nhật ký). Tôi muốn có một phương thức sẽ trả về danh sách tất cả các giá trị Enum được đặt trong một số biến. Trong ví dụ của tôiLiệt kê tất cả các tên bit từ một lá cờ Enum

[Flag] 
Enum HWResponse 
{ 
    None = 0x0, 
    Ready = 0x1, 
    Working = 0x2, 
    Error = 0x80, 
} 

Tôi nuôi nó 0x81, và nó sẽ cung cấp cho tôi với một IEnumerable<HWResponse> chứa {Ready, Error}.

Vì tôi không tìm thấy một cách đơn giản hơn, tôi đã cố gắng viết mã bên dưới, nhưng tôi không thể biên dịch được.

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
    throw new ArgumentException(); 

    List<T> toreturn = new List<T>(100); 

    foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>()) 
    { 
    Enum bit = ((Enum) curValueBit); // Here is the error 

    if (mask.HasFlag(bit)) 
     toreturn.Add(curValueBit); 
    } 

    return toreturn; 
} 

Trên phiên bản mã này, trình biên dịch phàn nàn rằng nó không thể truyền T đến Enum.

Tôi đã làm gì sai? Có cách nào tốt hơn (đơn giản hơn) để làm điều này? Làm thế nào tôi có thể làm cho các diễn viên?

Ngoài ra, tôi đã cố gắng để viết phương pháp như

public static IEnumerable<T> MaskToList<T>(Enum mask) where T:Enum 

nhưng Enum là một loại đặc biệt mà cấm cú pháp 'nơi' (Sử dụng C# 4.0)

+1

Điều này dường như không phải là một lá cờ; các kết hợp không có ý nghĩa. Có thể một cái gì đó được "làm việc" và "Ready" cùng một lúc? –

+0

@DBM: Điều này đúng, nó chỉ là một ví dụ ngớ ngẩn – PPC

+0

@All: Cảm ơn bạn đã có câu trả lời tuyệt vời của bạn. Tất cả 3 đều hữu ích! – PPC

Trả lời

19

Đây là một cách đơn giản để viết nó sử dụng LINQ:

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
    if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
     throw new ArgumentException(); 

    return Enum.GetValues(typeof(T)) 
         .Cast<Enum>() 
         .Where(m => mask.HasFlag(m)) 
         .Cast<T>(); 
} 
+2

Giải pháp tuyệt vời: đơn giản, dễ đọc và ngắn gọn. Một nhược điểm mặc dù: HasFlag (m) cũng liệt kê trạng thái 'Không' (0x0); nhưng rất dễ để khắc phục bằng một kiểu nhị phân cũ và – PPC

+1

Ngoài ra HasFlag() có vẻ có vấn đề về hiệu suất lớn: xem một chuỗi khác trên http://stackoverflow.com/a/7164314/1121983 – PPC

+0

Điều này có thực sự hoạt động không? Khi tôi cố gắng để đúc 'var value = (T) Enum.GetValues ​​(typeof (T)). Cast () .FirstOrDefault (m => mask.HasFlag (m))' trình biên dịch than phiền. – JobaDiniz

3

Nếu kết quả cuối cùng mong muốn của bạn là một danh sách chuỗi tên, chỉ cần gọi mask.ToString().

bạn sẽ làm gì nếu enum được xác định như thế này:

[Flags] 
enum State 
{ 
    Ready = 1, 
    Waiting = 2, 
    ReadyAndWaiting = 3 
} 

Như để giải quyết các lỗi biên dịch, điều này sẽ làm điều đó:

Enum bit = (Enum)(object)curValueBit; 

Jon Skeet có một dự án có tên unconstrained melody rằng cho phép bạn thêm ràng buộc enum, sau khi biên dịch, bằng cách viết lại IL. Điều này làm việc vì CLR hỗ trợ một ràng buộc như vậy, mặc dù C# không.

Một ý nghĩ: Nó sẽ hiệu quả hơn để đúc các giá trị trở lại của GetValues ​​trực tiếp đến T[]:

foreach(T curValueBit in (T[])Enum.GetValues(typeof (T))) 
+0

mask.ToString() thực sự là những gì tôi cuối cùng muốn (mặc dù tôi muốn có một giải pháp linh hoạt hơn). Có vẻ như một mã rất giống nhau được thực hiện trong khuôn khổ để cho phép kết quả như vậy. Tôi cũng muốn xem nó :) – PPC

+0

Ngoài ra, tôi không phải là fan của giải pháp 'ReadyAndWaiting': enum thực sự của tôi có 14 lá cờ, và tôi sẽ không thực hiện tất cả các giai đoạn có thể với những cái tên dài như vậy :) – PPC

+0

Cuối cùng nhưng không kém phần quan trọng, bạn có thể giải thích thêm một chút về dàn diễn viên cho T []? Vấn đề hiệu năng? – PPC

1

gì nếu chỉ làm một cái gì đó như thế này:

public static IEnumerable<T> MaskToList<T>(Enum mask) 
{ 
if (typeof(T).IsSubclassOf(typeof(Enum)) == false) 
    throw new ArgumentException(); 

    List<T> toreturn = new List<T>(100); 

    foreach(T curValueBit in Enum.GetValues(typeof (T)).Cast<T>()) 
    { 
    Enum bit = (curValueBit as Enum); // The only difference is actually here, 
             // use "as", instead of (Enum) cast 

    if (mask.HasFlag(bit)) 
     toreturn.Add(curValueBit); 
    } 

    return toreturn; 
} 

Khi as có không biên dịch kiểm tra thời gian. Trình biên dịch ở đây chỉ "tin tưởng" bạn, hy vọng rằng bạn biết mình đang làm gì, do đó, lỗi biên dịch không tăng lên.

+0

Tại sao không 'foreach (Enum curValueBit trong (T []) Enum.GetValues ​​(typeof (T)))'? Ngoài ra, vì tính hữu dụng như là một phần mở rộng, có thể tốt hơn nếu có mặt nạ T thay vì mặt nạ Enum – Random832

+0

@ Random832: bạn đã thử nó chưa? – Tigran

+0

Tuyên bố foreach hoạt động, mặc dù phần thân của vòng lặp cần sửa đổi và nó có thể có các đặc tính hiệu suất khác nhau. Tôi nghi ngờ các diễn viên để T [] thay vì .Cố luôn luôn là tốt hơn. Tôi sẽ lo lắng về trường hợp của phoog, với các giá trị được xác định có nhiều hơn một cờ. HasFlag làm gì trong trường hợp này? – Random832

2

xây dựng trên Gabe's answer tôi đến với điều này:

public static class EnumHelper<T> 
    where T : struct 
{ 
    // ReSharper disable StaticFieldInGenericType 
    private static readonly Enum[] Values; 
    // ReSharper restore StaticFieldInGenericType 
    private static readonly T DefaultValue; 

    static EnumHelper() 
    { 
     var type = typeof(T); 
     if (type.IsSubclassOf(typeof(Enum)) == false) 
     { 
      throw new ArgumentException(); 
     } 
     Values = Enum.GetValues(type).Cast<Enum>().ToArray(); 
     DefaultValue = default(T); 
    } 

    public static T[] MaskToList(Enum mask, bool ignoreDefault = true) 
    { 
     var q = Values.Where(mask.HasFlag); 
     if (ignoreDefault) 
     { 
      q = q.Where(v => !v.Equals(DefaultValue)); 
     } 
     return q.Cast<T>().ToArray(); 
    } 
} 

tôi tổ chức mọi thứ một chút khác nhau, cụ thể là tôi đặt việc kiểm tra loại (ví dụ: xác minh rằng T thực sự là một liệt kê) và thu được các giá trị enum trong hàm tạo tĩnh, do đó, điều này được thực hiện chỉ một lần (đây sẽ là một cải tiến hiệu suất ).

Một điều nữa, tôi đã thêm tham số tùy chọn để bạn có thể bỏ qua giá trị "không"/"Không"/"Không thể áp dụng"/"Không xác định"/etc điển hình của điều tra.

Các vấn đề liên quan