更新: 2021-12-20 (月)
投稿: 2020-09-11 (金)
[C#] EnumのFlagsを安心して使う方法
安心して使うには、0 は有効な値として使わず、None = 0、Default = 0 などと定義する。判定のHasFlag()は、.NET Core 2.1 から高速なので使い、それ以前ではビット演算で高速判定する。
環境
- .NET 6.0.101
- C# 10.0
- Visual Studio 2022 Version 17.0.1
- Windows 10 Pro 64bit 21H1 19043.1415
結論
定義
using System;
[Flags]
enum MyKeys // 命名は複数形 (Flags, Enumなどのサフィックスは付けない)
{
None = 0, // Default = 0, NoAccess = 0 など意味を持たない定義にする
A = 1 << 0, // 有効な値は1から始める
B = 1 << 1, // ビット左シフトで定義すると楽
C = 1 << 2, // シフト値を増やしていく
AC = A | C, // 複数の組み合わせ
}
// 以下の定義例1~4は、どれも同じ結果
// 1. 左シフト演算子 (シフト値をインクリメントするだけなのでオススメ)
[Flags]
enum MyKeys1
{
None = 0,
A = 1 << 0,
B = 1 << 1,
C = 1 << 2,
D = 1 << 3,
E = 1 << 4,
F = 1 << 5,
G = 1 << 6,
H = 1 << 7,
I = 1 << 8,
J = 1 << 9,
K = 1 << 10,
L = 1 << 11,
AC1 = A | C,
AC2 = (1 << 0 | 1 << 2),
}
// 2. 16進数リテラル
[Flags]
enum MyKeys2
{
None = 0,
A = 0x0001,
B = 0x0002,
C = 0x0004,
D = 0x0008,
E = 0x0010,
F = 0x0020,
G = 0x0040,
H = 0x0080,
I = 0x0100,
J = 0x0200,
K = 0x0400,
L = 0x0800,
AC1 = A | C,
AC2 = 0x0005, // 0x0001 + 0x0004 と同じ
}
// 3. 2進数リテラル (C# 7.0 以降)
// 0b_ のように 0b の後に _ を付けれるのは、C# 7.2 以降
[Flags]
enum MyKeys3
{
None = 0,
A = 0b_0000_0000_0001,
B = 0b_0000_0000_0010,
C = 0b_0000_0000_0100,
D = 0b_0000_0000_1000,
E = 0b_0000_0001_0000,
F = 0b_0000_0010_0000,
G = 0b_0000_0100_0000,
H = 0b_0000_1000_0000,
I = 0b_0001_0000_0000,
J = 0b_0010_0000_0000,
K = 0b_0100_0000_0000,
L = 0b_1000_0000_0000,
AC1 = A | C,
AC2 = 0b_0101, // 0b_0100 | 0b_0001 と同じ
}
// 4. 10進数リテラル
[Flags]
enum MyKeys4
{
None = 0,
A = 1,
B = 2,
C = 4,
D = 8,
E = 16,
F = 32,
G = 64,
H = 128,
I = 256,
J = 512,
K = 1024,
L = 2048,
AC1 = A | C,
AC2 = 5,
}
代入
var v = MyKeys.A; // v = A
// フラグ追加 (複数指定可)
v |= MyKeys.B | MyKeys.C; // v = A | B | C
// フラグ削除 (複数指定可)
v &= ~MyKeys.A & ~MyKeys.B ; // v = C
判定 (.NET Core 2.1 以降向け)
var ab = MyKeys.A | MyKeys.B;
// A を含む
ab.HasFlag(MyKeys.A); // true
// A ,B 両方を含む (and判定)
ab.HasFlag(MyKeys.A | MyKeys.B); // true
// A, B どちらかを含む (or判定)
(ab & (MyKeys.A | MyKeys.B)) > 0; // true
判定 (.NET Core 2.0以前、.NET Frameworkでの高速判定)
HasFlag()
の性能が悪いため、ビット演算判定を使用します。
var ab = MyKeys.A | MyKeys.B;
// A を含む
(ab & MyKeys.A) == MyKeys.A; // true
// A ,B 両方を含む (and判定)
(ab & (MyKeys.A | MyKeys.B)) == (MyKeys.A | MyKeys.B); // true
// A, B どちらかを含む (or判定)
(ab & (MyKeys.A | MyKeys.B)) > 0; // true
// よく使うEnumは拡張メソッドを使うと楽できる
static class EnumExtensions
{
public static bool HasBitFlag(this MyKeys value, MyKeys flag) => (value & flag) == flag;
}
// A を含む
ab.HasBitFlag(MyKeys.A);
// A ,B 両方を含む (and判定)
ab.HasBitFlag(MyKeys.A | MyKeys.B);
上記のサンプルコード
SharpLab
説明
0を意味ある定義にしない理由
[Flags]
enum ShibouFlags
{
A = 0, // A も有効なフラグのつもりで定義すると...
B = 1
}
var b = ShibouFlags.B;
// ShibouFlags.A が含まれていないのに true になるので、正しく判定できず死亡
b.HasFlag(ShibouFlags.A); // true
(参考) .NET の定義例
// 0 の定義があるのでOK
public enum ModifierKeys
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
// 0 の定義はないが、有効な値は1から始まる
[Flags]
public enum FileAccess
{
Read = 1,
Write = 2,
ReadWrite = 3,
}
HasFlagのパフォーマンス
Enumは、ボクシングとリフレクションで実行される部分があるので遅いです。
.NET Framework(.NET Core 2.0以前)ではHasFlag()は遅いので、大人しく拡張メソッドを作った方が良いです。
ジェネリックで実装できないため、ボクシングも回避した高速判定の実装をするには、さらに工夫が必要になるためです。
static class EnumExtensions
{
// Enumごとに拡張メソッドを作る
public static bool HasBitFlag(this MyKeys value, MyKeys flag) => (value & flag) == flag;
public static bool HasBitFlag(this ModifierKeys value, ModifierKeys flag) => (value & flag) == flag;
// 以下の方法はオススメできない
// 残念ながらジェネリックではコンパイルエラー
static bool HasBitFlag<T>(this T value, T flag) where T : Enum
{
// CS0019: 演算子 '&' を 'T' と 'T' 型のオペランドに適用することはできません
return (value & flag) == flag;
}
// Convert.ToUInt64() でボクシングが発生してしまう
public static bool HasBitFlagUInt64<T>(this T value, T flag) where T : Enum
{
return (Convert.ToUInt64(value) & Convert.ToUInt64(flag)) == Convert.ToUInt64(flag);
}
// Enum型の引数では、ボクシングが発生してしまう
public static bool HasBitFlag(this Enum value, Enum flag)
{
}
}
感謝
- HasFlag は .NET Core 2.1 からJIT対応で高速化
- HasFlagとビット演算の比較
- 実装解説
- 整数リテラル
- Enumを高速処理するライブラリ
- 質問
- c# - How to check if any flags of a flag combination are set? - Stack Overflow
- performance - C# Enum.HasFlag vs. Bitwise AND Operator Check - Stack Overflow
- c# - What is it that makes Enum.HasFlag so slow? - Stack Overflow
- .net - How to Compare Flags in C#? - Stack Overflow
- C# HasAFlag method extension: how to not create garbage allocation? - Unity Forum
関連記事
新着記事