ถ้าฉันมีตัวแปรที่ถือแฟล็ก enum ฉันจะวนซ้ำค่าบิตในตัวแปรนั้นได้หรือไม่ หรือฉันต้องใช้ Enum.GetValues เพื่อวนซ้ำรอบ enum ทั้งหมดและตรวจสอบว่ามีการตั้งค่าใด
ถ้าฉันมีตัวแปรที่ถือแฟล็ก enum ฉันจะวนซ้ำค่าบิตในตัวแปรนั้นได้หรือไม่ หรือฉันต้องใช้ Enum.GetValues เพื่อวนซ้ำรอบ enum ทั้งหมดและตรวจสอบว่ามีการตั้งค่าใด
คำตอบ:
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
HasFlag
พร้อมใช้งานตั้งแต่. NET 4 เป็นต้นไป
Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
จากนั้นเพียง: myEnum.GetFLags()
:)
static IEnumerable<Enum> GetFlags(this Enum input)
นี่คือวิธีแก้ปัญหาของ Linq
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));
ถ้าคุณมีค่าเป็นศูนย์เพื่อแสดงNone
ไม่มีเมธอดในตัวเพื่อรับแต่ละองค์ประกอบเท่าที่ฉันรู้ แต่นี่เป็นวิธีหนึ่งที่คุณจะได้รับ:
[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
ฉันปรับสิ่งที่Enum
ทำภายในเพื่อสร้างสตริงเพื่อส่งคืนแฟล็กแทน คุณสามารถดูรหัสในตัวสะท้อนแสงและควรมีค่าเทียบเท่ากันมากหรือน้อย ใช้ได้ดีสำหรับกรณีการใช้งานทั่วไปที่มีค่าที่มีหลายบิต
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;
}
}
}
เมธอดส่วนขยายGetIndividualFlags()
รับแฟล็กทั้งหมดสำหรับแต่ละประเภท ดังนั้นค่าที่มีหลายบิตจึงถูกละไว้
var value = Items.Bar | Items.Baz;
value.GetFlags(); // Boo
value.GetIndividualFlags(); // Bar, Baz
Bar
, Baz
และแทนเพียงBoo
Boo
Boo
(ค่าที่ส่งคืนโดยใช้ToString()
) ฉันได้ปรับแต่งให้อนุญาตเฉพาะแฟล็กแต่ละรายการ ดังนั้นในตัวอย่างของฉันคุณจะได้รับBar
และแทนBaz
Boo
กลับมาในอีกไม่กี่ปีต่อมาด้วยประสบการณ์ที่มากขึ้นอีกเล็กน้อยคำตอบที่ดีที่สุดของฉันสำหรับค่าบิตเดียวเท่านั้นโดยเปลี่ยนจากบิตต่ำสุดไปยังบิตสูงสุดเป็นตัวแปรเล็กน้อยของกิจวัตรภายในของ 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;
}
}
}
ดูเหมือนว่าจะใช้งานได้และแม้จะมีการคัดค้านของฉันเมื่อหลายปีก่อนฉันใช้ HasFlag ที่นี่เนื่องจากมันชัดเจนกว่าการใช้การเปรียบเทียบแบบบิตและความแตกต่างของความเร็วนั้นไม่มีนัยสำคัญสำหรับทุกสิ่งที่ฉันจะทำ (เป็นไปได้ทั้งหมดที่พวกเขาได้ปรับปรุงความเร็วของ HasFlags ตั้งแต่นั้นมาสำหรับทั้งหมดที่ฉันรู้ ... ฉันยังไม่ได้ทดสอบ)
yield return bits;
?
ออกจากวิธีการของ @ Greg แต่เพิ่มคุณสมบัติใหม่จาก C # 7.3 Enum
ข้อ จำกัด :
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;
}
ข้อ จำกัด ใหม่ที่ช่วยให้นี้จะเป็นวิธีขยายโดยไม่ต้องโยนผ่าน(int)(object)e
และฉันสามารถใช้HasFlag
วิธีการและโยนโดยตรงไปจากT
value
C # 7.3 ยังเพิ่มข้อ จำกัด สำหรับค่าเดลาเกตและunmanaged
.
flags
พารามิเตอร์เป็นประเภททั่วไปT
ด้วยมิฉะนั้นคุณจะต้องระบุประเภท enum อย่างชัดเจนทุกครั้งที่เรียกใช้
+1 สำหรับคำตอบโดย @ RobinHood70 ฉันพบว่าวิธีการทั่วไปนั้นสะดวกสำหรับฉัน
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;
}
}
}
แก้ไข และ +1 สำหรับ @AustinWBryan เพื่อนำ C # 7.3 เข้าสู่พื้นที่โซลูชัน
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;
}
}
}
คุณไม่จำเป็นต้องวนซ้ำค่าทั้งหมด เพียงตรวจสอบแฟล็กเฉพาะของคุณดังนี้:
if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1)
{
//do something...
}
หรือ (ตามที่pstrjdsกล่าวในความคิดเห็น) คุณสามารถตรวจสอบการใช้งานได้เช่น:
if(myVar.HasFlag(FlagsEnum.Flag1))
{
//do something...
}
สิ่งที่ฉันทำคือเปลี่ยนแนวทางของฉันแทนที่จะพิมพ์พารามิเตอร์อินพุตของเมธอดเป็นenum
ประเภทฉันพิมพ์เป็นอาร์เรย์ของenum
ประเภท ( MyEnum[] myEnums
) วิธีนี้ฉันจะวนซ้ำผ่านอาร์เรย์ด้วยคำสั่งสวิตช์ภายในลูป
ไม่พอใจกับคำตอบข้างต้นแม้ว่าจะเป็นการเริ่มต้นก็ตาม
หลังจากรวบรวมแหล่งข้อมูลต่างๆที่นี่:
โปสเตอร์ก่อนหน้าใน
โครงการSO QnA Code ของเธรดนี้Enum Flags ตรวจสอบโพสต์
Great Enum <T> Utility
ฉันสร้างสิ่งนี้ขึ้นมาเพื่อบอกให้ฉันรู้ว่าคุณคิดอย่างไร
พารามิเตอร์::
bool checkZero
บอกให้อนุญาต0
เป็นค่าแฟล็ก โดยค่าเริ่มต้นinput = 0
จะคืนค่าว่างเปล่า
bool checkFlags
: บอกให้ตรวจสอบว่าEnum
มีการตกแต่งด้วย[Flags]
แอตทริบิวต์หรือไม่
PS ตอนนี้ฉันไม่มีเวลาหาcheckCombinators = false
alg ซึ่งจะบังคับให้ไม่สนใจค่า enum ใด ๆ ที่เป็นชุดค่าผสมของบิต
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;
}
}
}
สิ่งนี้ใช้ประโยชน์จาก Helper Class Enum <T>ที่ฉันอัปเดตเพื่อใช้yield return
สำหรับ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);
}
}
สุดท้ายนี่คือตัวอย่างการใช้งาน:
private List<CountType> GetCountTypes(CountType countTypes)
{
List<CountType> cts = new List<CountType>();
foreach (var ct in countTypes.GetFlags())
cts.Add(ct);
return cts;
}
จากคำตอบของ Greg ข้างต้นสิ่งนี้ยังดูแลกรณีที่คุณมีค่า 0 ใน enum ของคุณเช่น None = 0 ในกรณีนี้ไม่ควรวนซ้ำเกินค่านั้น
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;
}
ใครจะรู้วิธีปรับปรุงสิ่งนี้ให้ดียิ่งขึ้นเพื่อที่จะสามารถจัดการกับกรณีที่แฟล็กทั้งหมดใน enum ถูกตั้งค่าด้วยวิธีที่ชาญฉลาดที่สามารถจัดการประเภท enum ที่อยู่ข้างใต้ทั้งหมดและกรณีของ All = ~ 0 และ All = EnumValue1 | EnumValue2 | EnumValue3 | ...
คุณสามารถใช้ Iterator จาก Enum เริ่มต้นจากรหัส 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];
}
}
}
}
ยังไม่ผ่านการทดสอบดังนั้นหากฉันทำผิดพลาดอย่าลังเลที่จะโทรหาฉัน (เห็นได้ชัดว่าไม่ใช่ re-entrant) คุณต้องกำหนดค่าไว้ล่วงหน้าหรือแยกออกเป็นฟังก์ชันอื่นที่ใช้ enum.dayflag และ enum.days คุณอาจจะไปที่ไหนสักแห่งด้วยโครงร่าง
อาจเป็นเช่นเดียวกับรหัสต่อไปนี้:
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(',');
}
จะเข้าไปข้างในก็ต่อเมื่อมีค่า enum อยู่ในค่าเท่านั้น
คำตอบทั้งหมดทำงานได้ดีกับแฟล็กธรรมดาคุณอาจจะมีปัญหาเมื่อรวมแฟล็กเข้าด้วยกัน
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
อาจต้องเพิ่มการตรวจสอบเพื่อทดสอบว่าค่า enum ของตัวเองเป็นชุดค่าผสมหรือไม่ คุณอาจต้องการบางอย่างเช่นที่Henk van Boeijenโพสต์ไว้ที่นี่ เพื่อให้ครอบคลุมความต้องการของคุณ (คุณต้องเลื่อนลงเล็กน้อย)
วิธีการขยายโดยใช้ข้อ จำกัด Enum และ generics ใหม่เพื่อป้องกันการหล่อ:
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();
}
}
คุณสามารถทำได้โดยตรงโดยการแปลงเป็น int แต่คุณจะตรวจสอบประเภทหลวม ฉันคิดว่าวิธีที่ดีที่สุดคือใช้สิ่งที่คล้ายกับโจทย์ของฉัน รักษาประเภทที่เหมาะสมตลอดทาง ไม่จำเป็นต้องแปลง มันไม่สมบูรณ์แบบเนื่องจากการชกมวยซึ่งจะเพิ่มประสิทธิภาพการตีเล็กน้อย
ไม่เพอร์เฟกต์ (หมวย) แต่ทำงานแบบไม่มีคำเตือน ...
/// <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;
}
}
}
การใช้งาน:
FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
foreach (FileAttributes fa in att.GetFlags())
{
...
}