フラグの列挙型を保持する変数がある場合、その特定の変数のビット値をどうにかして反復できますか?または、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;
}
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
私の知る限り、各コンポーネントを取得するための組み込みメソッドはありません。しかし、ここにそれらを取得する1つの方法があります。
[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
。
数年後にこれに戻って、もう少し経験を積んで、最低ビットから最高ビットに移動する単一ビット値のみに対する私の最終的な答えは、ジェフメルカドの内部ルーチンのわずかな変形です:
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
もジェネリック型にする必要がありました。そうでない場合は、呼び出すたびに列挙型を明示的に指定する必要があります。
@ 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;
}
}
}
EDIT および@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 Flags Check 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;
}
}
}
これは、ここで使用するyield return
ために更新した、ここにあるヘルパークラスEnum <T>を利用し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の答えに基づいて、これは、None = 0など、列挙型に値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のイテレータを使用できます。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(',');
}
enum値が値に含まれている場合にのみ、内部に入ります
すべての回答は単純なフラグでうまく機能します。フラグを組み合わせると、おそらく問題が発生します。
[Flags]
enum Food
{
None=0
Bread=1,
Pasta=2,
Apples=4,
Banana=8,
WithGluten=Bread|Pasta,
Fruits = Apples | Banana,
}
おそらく、列挙値自体が組み合わせであるかどうかをテストするためのチェックを追加する必要があります。 要件をカバーするには、おそらくHenk van Boeijenがここに投稿したようなものが必要です(少し下にスクロールする必要があります)。
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())
{
...
}