System.Collections.Specialized.BitVector32 は、32 ビット整数 1 つを構造体としてラップし、ビットフラグや小さな複数フィールドのパッキングを効率的に扱う ための型です。同じ「ビットの集合」でも BitArray がヒープ上のクラスで可変長なのに対し、BitVector32 は 値型・スタック割り当て・固定 32 ビット で、軽量さに振り切っているのが特徴です。
BitVector32 とは
- 名前空間:
System.Collections.Specialized - 構造体(
struct)。サイズはちょうど 4 バイト(int1 つ分) - 32 ビットしか入らない(固定長)
- 二つの使い方が用意されている
- ビットマスク:個々のビットを ON/OFF するフラグ用
- セクション:連続するビット範囲を「小さな整数フィールド」として読み書き
using System.Collections.Specialized;
var bits = new BitVector32(0); // 全ビット 0
// ビットマスク:1 ビットを順に取得
int bit0 = BitVector32.CreateMask(); // 0b0001
int bit1 = BitVector32.CreateMask(bit0); // 0b0010
int bit2 = BitVector32.CreateMask(bit1); // 0b0100
bits[bit0] = true; // ビット 0 を立てる
bool b = bits[bit2]; // ビット 2 を読む
サポートするインターフェース
BitVector32 は値型で、インターフェースは何も実装していません(コレクションとしての列挙すらない)。Equals / GetHashCode / ToString をオーバーライドしているのみで、ICollection や IEnumerable は持ちません。
これは BitVector32 が「コレクション」というより int 1 つに対する型付きの便利ラッパ だからです。System.Collections.Specialized 名前空間に置かれてはいますが、設計思想は BitArray よりむしろ [Flags] enum に近いと考えてよいです。
ビット集合をコレクションらしく扱いたい場面では BitArray を、列挙との連携が必要な場面では IEnumerable を実装する他のコレクションを使ってください。
ビットマスクの使い方
CreateMask() は次のビット位置を指すマスクを順に作るユーティリティです。
int isVisible = BitVector32.CreateMask(); // 1 << 0
int isEnabled = BitVector32.CreateMask(isVisible); // 1 << 1
int isSelected = BitVector32.CreateMask(isEnabled); // 1 << 2
var state = new BitVector32(0);
state[isVisible] = true;
state[isEnabled] = true;
if (state[isVisible] && !state[isSelected])
Console.WriteLine("visible but not selected");
[Flags] enum でも同等のことはできますが、BitVector32 は マスクを動的に組み立てたい ときや、後述の セクションと併用したい ときに便利です。
セクション(ビットフィールド)
連続する N ビットを「0 〜 (2^N - 1) の整数」として読み書きできる仕組みが Section です。CreateSection(maxValue) で必要なビット幅が自動決定されます。
// 例:32 ビットに以下を詰め込む
// - PageIndex (0–4095, 12 ビット)
// - PageSize (0–255, 8 ビット)
// - Mode (0–7, 3 ビット)
BitVector32.Section pageIndex = BitVector32.CreateSection(4095);
BitVector32.Section pageSize = BitVector32.CreateSection(255, pageIndex);
BitVector32.Section mode = BitVector32.CreateSection(7, pageSize);
var packed = new BitVector32(0);
packed[pageIndex] = 100;
packed[pageSize] = 50;
packed[mode] = 3;
int rawData = packed.Data; // int として取り出してファイル/ネットワークに送る
var restored = new BitVector32(rawData);
Console.WriteLine(restored[pageIndex]); // 100
ポイント:
CreateSection(maxValue)には「格納したい最大値」を渡す。必要なビット幅は自動計算(maxValueを 2 進表現したときの桁数)。- 第 2 引数に 直前のセクション を渡すと、その隣のビット位置から始まる新しいセクションが得られる。
- 全セクションの合計ビット幅は 32 を超えてはいけない。
これは C 言語のビットフィールドや、ファイルフォーマット・ネットワークプロトコルの パックされたヘッダ を扱うときに非常に便利です。
BitVector32 vs BitArray vs [Flags] enum
| 観点 | BitVector32 |
BitArray |
[Flags] enum |
|---|---|---|---|
| 種別 | 値型(struct) | 参照型(class) | 値型(int 等) |
| 長さ | 固定 32 ビット | 可変(任意) | 32/64 ビット |
| ヒープ割り当て | なし | あり | なし |
| セクション(ビットフィールド) | ◯ | × | × |
| ビット演算(コレクション間) | × | ◯(And / Or / Xor / Not) |
ビット演算子で可 |
列挙(IEnumerable) |
× | ◯ | × |
| 用途 | 32 ビット内の高密度パッキング | 大規模なビット集合 | 名前付きフラグ |
計算量
すべての操作が O(1):単純なビットマスクとシフトしかしないためです。値型なのでコピーも 4 バイトのコピー 1 回で済みます。
使いどころ
1. 32 個以下のフラグをまとめて扱う
オブジェクトの状態フラグを 1 個の int に圧縮したいとき。[Flags] enum でも代用可能ですが、BitVector32 は実行時に マスクを動的に作り出せる ところに利点があります。
public sealed class Cell
{
private BitVector32 flags;
public static readonly int IsVisible = BitVector32.CreateMask();
public static readonly int IsEnabled = BitVector32.CreateMask(IsVisible);
public static readonly int IsSelected = BitVector32.CreateMask(IsEnabled);
public bool this[int mask]
{
get => flags[mask];
set => flags[mask] = value;
}
}
2. 複数の小さな整数フィールドをひとつの int に詰める
シリアル化サイズを抑えたい、レジスタ/プロトコルヘッダを表現したい、メモリ局所性を高めたい——といった場面で有効。
3. 大量のオブジェクトに少しずつフラグを持たせる
数百万個のレコードに bool IsActive、bool IsArchived、bool IsDirty をそれぞれ持たせると参照型では浪費が大きくなります。BitVector32 を 1 つ持たせれば 4 バイトに収まるので、メモリと GC 圧の両方を改善できます。
注意点
- 値型なのでコピーされる:プロパティで
BitVector32を返すと書き換えが伝播しません。フィールドとして直接保持するか、refで渡す設計を選びます。 - 32 ビットを超える要件は不可:超えるなら
BitArrayかlongベースの自作型を検討。 - デバッグ表示:
ToStringがビット表現を整形してくれるのでデバッグ時に便利。
まとめ
BitVector32は 32 ビットを構造体でラップした高効率ビットフラグ/パック型。- インターフェースは何も実装しない。
intの便利ラッパとしての位置づけ。 - すべての操作が O(1)、ヒープ割り当てなし。
CreateMaskでフラグ用のビットマスクを、CreateSectionで「小さな整数フィールド」を 32 ビットに詰め込める。- 32 ビットを超える集合や、コレクション同士のビット演算が必要なときは BitArray を選ぶ。