如果我有一个变量持有一个标志枚举,我是否可以以某种方式迭代该特定变量中的单个位值?或者我必须使用Enum.GetValues遍历整个枚举并检查哪些被设置了?
如果我有一个变量持有一个标志枚举,我是否可以以某种方式迭代该特定变量中的单个位值?或者我必须使用Enum.GetValues遍历整个枚举并检查哪些被设置了?
static IEnumerable<Enum> GetFlags(Enum input)
{
foreach (Enum value in Enum.GetValues(input.GetType()))
if (input.HasFlag(value))
yield return value;
}
这里是一个解决问题的 Linq 解决方案。
public static IEnumerable<Enum> GetFlags(this Enum e)
{
return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}
var zero = (T)Convert.ChangeType(0, Enum.GetUnderlyingType(typeof(T)));
并检查.Where(enumValue => !Equals(enumValue, zero))
。 - Tobias Knauss据我所知,没有内置方法可以获得每个组件。但是这里有一种方法可以获取它们:
[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
Boo
(使用ToString()
返回的值)。我已经进行了调整,只允许单个标志。所以在我的示例中,你可以获得Bar
和Baz
而不是Boo
。 - Jeff MercadoBitOperations.IsPow2()
函数,可以将GetFlagValues
简化为一行代码。 - undefined几年后回来,经验更加丰富,对于仅限单个比特值的情况,从最低位到最高位移动,我的最终答案是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的速度,我并没有测试。)
参考@Greg的方法,但加入了C# 7.3的新功能,即Enum
约束:
public static IEnumerable<T> GetUniqueFlags<T>(this T 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
方法并直接从value
转换为T
。unmanaged
添加了约束。+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;
}
}
}
编辑 并且点赞@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;
}
}
}
使用新的枚举约束和泛型编写扩展方法,以避免强制类型转换:
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();
}
}
这是另一种使用Linq的C# 7.3解决方案。
using System;
using System.Collections.Generic;
using System.Linq;
public static class FlagEnumExtensions
{
public static IEnumerable<T> GetFlags<T>(this T en) where T : struct, Enum
{
return Enum.GetValues<T>().Where(member => en.HasFlag(member)).ToArray();
}
}
编辑:添加 .ToArray()
以防止多次枚举。
为了让代码更短,这是我的最新版本。 (我是OP...长故事。)如之前所讨论的,它会忽略None和多位值。
请注意,此版本使用了枚举约束和变量模式,因此至少需要C# 7.3。
public static IEnumerable<T> GetUniqueFlags<T>(this T value)
where T : Enum
{
var valueLong = Convert.ToUInt64(value, CultureInfo.InvariantCulture);
foreach (var enumValue in value.GetType().GetEnumValues())
{
if (
enumValue is T flag // cast enumValue to T
&& Convert.ToUInt64(flag, CultureInfo.InvariantCulture) is var bitValue // convert flag to ulong
&& (bitValue & (bitValue - 1)) == 0 // is this a single-bit value?
&& (valueLong & bitValue) != 0 // is the bit set?
)
{
yield return flag;
}
}
}
GetEnumValues()
的结果转换为T[]
并消除enumValue is T flag
条件,但这取决于GetEnumValues
始终返回类型为T的数组,而这并非契约上要求的。 - RobinHood70在游戏开发中,性能至关重要。由于垃圾分配和速度缓慢,所有给出的解决方案都很糟糕。但这是一个无垃圾分配、速度比被接受的答案快100倍以上的解决方案。
[Flags]
public enum PersonalTraits : short
{
None = 1 << 0,
Strength = 1 << 1,
Agility = 1 << 2,
Attack = 1 << 3,
Defence = 1 << 4,
Vitality = 1 << 5,
Stamina = 1 << 6,
Accuracy = 1 << 7,
Perception = 1 << 8,
Charisma = 1 << 9,
}
PersonalTraits athlete = PersonalTraits.Stamina | PersonalTraits.Strength;
for (short i = 0, value = 0; value <= (short)PersonalTraits.Charisma; i++, value = (short)(1 << i))
if (((short)athlete & value) != 0)
yield return (PersonalTraits)value;
HasFlag
方法仅适用于 .NET 4 及以上版本。 - Andreas GrechEnum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag);
然后只需要使用:myEnum.GetFLags()
:) - joshcomleyNone
的情况 - 例如,Jeff的答案中的Items.None
将始终包含在内”。 - Ilanstatic IEnumerable<Enum> GetFlags(this Enum input)
。 - user2609980