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 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?
Câu trả lời:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
có sẵn từ .NET 4 trở đi.
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
Sau đó, chỉ cần: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
Đâ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);
}
.Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));
nếu bạn có giá trị bằng 0 để đại diệnNone
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ì Enum
bê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
Bar
, Baz
và Boo
thay vì chỉ Boo
.
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 Bar
và Baz
thay vì Boo
.
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.)
yield return bits;
?
Đ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, Enum
rà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)e
và tôi có thể sử dụng HasFlag
phương thức đó và truyền trực tiếp T
từ đó value
.
C # 7.3 cũng đã thêm các ràng buộc cho delagates và unmanaged
.
flags
tham 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ó.
+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;
}
}
}
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...
}
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à enum
kiểu, tôi đã gõ nó dưới dạng một mảng của enum
kiể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.
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 checkZero
bảo nó cho phép 0
dưới dạng giá trị cờ. Theo mặc định input = 0
trả về sản phẩm nào.
bool checkFlags
: bảo nó kiểm tra xem Enum
trang 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 = false
alg 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 return
cho 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;
}
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 | ...
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.
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ị
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)
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();
}
}
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())
{
...
}