如果我有一个持有标志枚举的变量,我可以以某种方式迭代该特定变量中的位值吗?还是我必须使用Enum.GetValues遍历整个枚举并检查设置了哪些枚举?
如果我有一个持有标志枚举的变量,我可以以某种方式迭代该特定变量中的位值吗?还是我必须使用Enum.GetValues遍历整个枚举并检查设置了哪些枚举?
Answers:
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
from value
。
C#7.3还为滞后和限制添加了约束unmanaged
。
flags
参数也为通用类型T
,否则每次调用它时都必须显式指定枚举类型。
为@ RobinHood70提供的答案+1。我发现该方法的通用版本对我来说很方便。
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;
}
}
}
编辑 并为@AustinWBryan +1,以将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
)的数组,这样,我就在循环内使用switch语句遍历该数组。
尽管只是开始,但对上述答案并不满意。
在这里拼凑了一些不同的来源之后:
此线程的SO QnA
代码项目Enum标志中的上一个发布者检查Post
Great Enum <T>实用程序
我创建了这个,所以让我知道您的想法。
参数::
bool checkZero
告诉它允许0
作为标志值。默认情况下input = 0
返回空。
bool checkFlags
:告诉它检查是否Enum
用[Flags]
属性修饰了。
PS。我现在没有时间弄清楚checkCombinators = false
alg,这将迫使它忽略作为位组合的任何枚举值。
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;
}
}
}
这使得使用助手类的枚举<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的情况,例如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;
}
任何人都不会知道如何进一步改进它,以便它可以处理以超级聪明的方式设置枚举中的所有标志的情况,该方式可以处理所有基础枚举类型,以及All =〜0和All = EnumValue1 | EnumValue2 | EnumValue3 | ...
您可以使用Enum中的Iterator。从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];
}
}
}
}
它未经测试,所以如果我犯了一个错误,请随时叫我出来。(显然,它不是可重入的。)您必须事先分配值,或者将其分解为另一个使用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(',');
}
仅当枚举值包含在值中时,它才会进入内部
所有答案都可以通过简单的标志很好地使用,当组合标志时,您可能会遇到问题。
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
可能需要添加检查以测试自身的枚举值是否为组合。您可能需要类似Henk van Boeijen在此处发布的内容 来满足您的要求(您需要向下滚动一点)
使用新的Enum约束和泛型来防止强制转换的扩展方法:
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())
{
...
}