HOWTO · Csharp
C# でビットマスクを使用する
この記事では、C# でビットマスクを使用する方法を学習します。
このページの内容
ビットマスクとは、個々のビットが独立したフラグを表す整数です。たとえば、アクセス許可を表す値には、読み取り、書き込み、実行の設定を1つの数値に格納できます。C#のビット演算子を使うと、それらのビットをテスト、抽出、設定、解除、切り替え、反転、シフトできます。どのフラグを操作するのか明確になるよう、名前付きのマスクを使いましょう。
ビットマスクとビット演算子を理解する
各ビットは 0 または 1 です。演算子は対応するビット同士を比較します。AND (&) は両方の入力が 1 のときだけ 1 を返します。OR (|) はどちらかの入力が 1 なら 1 を返し、XOR (^) は入力が異なるときに 1 を返します。NOT (~) は整数オペランドのすべてのビットを反転します。シフト演算子 (<< と >>) は、指定した数だけビットを移動します。
| 演算 | 各ビットに対する意味 |
|---|---|
x & y |
両方の値でセットされている場合にだけ、そのビットを残します。 |
| `x | y` |
x ^ y |
一方の値だけでセットされている場合に、そのビットをセットします。 |
~x |
整数表現のすべてのビットを反転します。 |
マスクとは、対象の位置が 1、それ以外の位置が 0 になっている値です。Read = 1 や Write = 2 のような名前付き定数を使うと、それぞれ別のビット位置を選べます。
& でビットをテスト・抽出する
フラグをテストするには、値とそのマスクのANDを取ります。結果がゼロでなければ、そのフラグはセットされています。たとえば、if ((permissions & Read) != 0) のように書けます。マスクによって、それ以外の位置のビットがすべてクリアされるためです。複数のビットを抽出するには、対象の位置を含むマスクを使います。結果には選択したビットだけが残ります。たとえば、permissions & (Read | Write) は読み取りフラグと書き込みフラグをまとめて抽出します。
| でビットをセットする
値とフラグのマスクのORを取ると、ほかのビットを保ったまま、そのマスクに含まれるビットをセットできます。複合代入 permissions |= Write は、permissions | Write の結果を permissions に代入する処理の短縮形です。すでにセットされているフラグはセットされたままなので、フラグを確実に有効にしたい場合に適しています。
^ でビットを切り替える
マスクとのXORは、マスクで 1 になっている位置だけを反転します。たとえば、permissions ^= Execute のように書けます。セットされたビットはクリアされ、クリアされたビットはセットされます。意図的に状態を切り替える場合には便利ですが、フラグをオンにした状態に保つ操作ではありません。2回適用すると元の値に戻ります。
~ でビットを反転する
NOTは、アプリケーションでフラグに使っていない位置も含め、整数型のすべてのビットを反転します。既知のフラグをクリアするには、反転したマスクとのANDを組み合わせます。たとえば、permissions &= ~Write とします。ほかのフラグの位置は変わりません。反転した値を固定幅のビット列として表示する場合は、先にその幅に合わせてマスクしてください。~value の数値自体は、表示対象のフラグだけに制限されません。
<< と >> でビットをシフトする
左シフトはビットを上位の位置へ移動します。非負の値で、シフト後の結果が型に収まる場合、value << count は2のべき乗を掛ける操作に相当します。右シフトはビットを下位の位置へ移動し、非負の値では、シフトした分だけ下位のビットを捨てます。符号付きの負の値では、C#の右シフトは符号ビットを保持します。そのため、すべての右シフトでゼロが挿入されるとは説明できません。論理右シフトが必要な場合は符号なし型を使い、整数型に適したシフト数を指定してください。
シフトはマスクを簡潔に作る方法でもあります。小さな非負のシフト数なら、1 << 0、1 << 1、1 << 2 で連続するビット位置をそれぞれ選べます。アプリケーションのコードでは、通常、名前付き定数のほうが読みやすくなります。
実例でフラグを組み合わせる
このプログラムは、権限がない状態から始め、読み取りと書き込みを有効にします。次に、それらのフラグをテストして抽出し、書き込みを解除してから、実行フラグを切り替えます。表示される各値は、対応する操作後の状態を表します。この例では、完全な Main エントリポイントと10進数のマスクを使います。
次の例を実行してください。
このセクションのコードと表示されるラベルや出力は英語のままです。
using System;
class Program
{
static void Main()
{
const int Read = 1;
const int Write = 2;
const int Execute = 4;
int permissions = 0;
permissions |= Read | Write;
Console.WriteLine((permissions & Read) != 0);
Console.WriteLine(permissions & (Read | Write));
permissions &= ~Write;
permissions ^= Execute;
Console.WriteLine(permissions);
}
}
最初の操作では、ORを使って2つのフラグをセットします。テストでは Read がセットされているかどうかを表示し、抽出では選択したビットを数値で表示します。&= ~Write で書き込みを解除すると、書き込みビットだけが取り除かれます。その後、XORで実行フラグを切り替えます。マスクと格納する値には互換性のある整数型を使い、意図する位置が明確になるよう名前付きフラグを使いましょう。