C#でビット操作を簡単に行うラッパークラスの紹介

例えばC#でハードウェアに近い I/O を扱う場合、1チャンネルが ushort の1ブロックの読み書きが要求されていて、各ポートはビットごとに割り当てられているなんてケースが割とありますがいちいち出力ポートの設定を読んでビット操作をしてまた書き込むなどの操作はまぁ一部のユースケース以外に許されないと思います。

というか出力側は自分で値を保持していて管理値を送信するのが基本だと思います。

そういった場面でいちいちビット演算しているのはなかなか効率が悪いので「あるビット幅の値を操作を簡単に行えるクラス」というのを考えてみました。

確認環境

確認環境は以下の通りです。

  • VisualSturio2019
  • .NET 5.0
  • Windows10, コンソールプロジェクト

実装コード

さっそく実装コードの紹介です。

使い方

まずは使い方からです。ビット操作を行うクラス「BitManager」があます。そこに初期値を設定したインスタンスを作成し、「SetBit」「GetBit」 で各ビットを操作し、「ToUInt」や「ToULong」などでプリミティブな型に戻すことができます。

static void Main(string[] args)
{
    // 二進数で 1010 1010 1010 1010
    ulong value = 0xAAAA;

    // Long型の管理オブジェクトを作成
    BitManager bm = BitManager.Parse(value);

    // 内容を文字列に変換
    Console.WriteLine(bm.ToString());
    // > 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1010 1010 1010 1010

    // 3ビット目を1に変更
    bm.SetBit(2, true);
    // 2ビット目を0に変更
    bm.SetBitInt(1, 0);

    // 内容を文字列に変換
    Console.WriteLine(bm.ToString());
    // > 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1010 1010 1010 1100

    var v = bm.ToULong();
    Console.WriteLine("0x" + v.ToString("X16"));
    // > 0x000000000000AAAC
}

BitManager

「BitManager」の実装コードです。ユースケースによって作成した BitManager の値をプリミティブ型などで作成済みのオブジェクトの値を上書きしたいケースがあると思いますが、今回作成したクラスではサポートしていません。

/// <summary>
/// 任意のビット数を管理するためのクラスです。
/// </summary>
public class BitManager
{
    //
    // Fields
    // - - - - - - - - - - - - - - - - - - - -

    // true: 1 / false: 0 として bool で管理する
    private readonly bool[] bits;

    //
    // props
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// 現在管理中のビット数を取得します。
    /// </summary>
    public int BitLength => this.bits.Length;

    /// <summary>
    /// 管理ビット数を指定してオブジェクトを作成します。
    /// </summary>
    public BitManager(int bitCount) => this.bits = new bool[bitCount];

    /// <summary>
    /// 既定のサイズを指定してオブジェクトを作成します。
    /// </summary>
    public BitManager(BitSize size) : this(size.Value) { }

    //
    // Public Methods
    // - - - - - - - - - - - - - - - - - - - -

    // 指定したビットを設定または取得する
    public bool this[int index] { get => this.GetBit(index); set => this.SetBit(index, value); }

    public void SetBit(int bit, bool value) => this.bits[bit] = value; // チェックしない
    public void SetBitInt(int bit, int value) => this.bits[bit] = value != 0;
    public bool GetBit(int bit) => this.bits[bit];
    public int GetBitInt(int bit) => this.bits[bit] ? 1 : 0;

    // 任意のプリミティブ型から BitManager を作成する
    public static BitManager Parse(ulong value) 
        => ParseCommon(new BitManager(BitSize.Long), value);
    public static BitManager Parse(uint value)
        => ParseCommon(new BitManager(BitSize.Int), value);
    public static BitManager Parse(ushort value)
        => ParseCommon(new BitManager(BitSize.Short), value);
    public static BitManager Parse(byte value)
        => ParseCommon(new BitManager(BitSize.Byte), value);

    // 現在の管理値を指定したプリミティブ型へ変換する
    public ulong ToULong() => ToValue(BitSize.Long);
    public uint ToUInt() => (uint)ToValue(BitSize.Int);
    public ushort ToUShort() => (ushort)ToValue(BitSize.Short);
    public byte ToByte() => (byte)ToValue(BitSize.Byte);

    // 全てのビットを列挙する
    public int[] GetAllBitsInt()
    {
        var ret = new int[this.bits.Length];
        for (int i = 0; i < ret.Length; i++)
        {
            ret[i] = this.bits[i] ? 1 : 0;
        }
        return ret;
    }
    public bool[] GetAllBitsBool()
    {
        var ret = new bool[this.bits.Length];
        Array.Copy(this.bits, ret, ret.Length);
        return ret;
    }

    // 2進数で4ビット区切りで出力
    public override string ToString()
    {
        List<bool> list = new (this.bits); 
        list.Reverse();
        int i = 0;
        StringBuilder sb = new StringBuilder();
        list.ForEach(n =>
        {
            if (i++ == 4)
            {
                i = 1;
                sb.Append(' ');
            }
            sb.Append(n ? 1 : 0);
        });
        return sb.ToString();
    }

    //
    // Private Methods
    // - - - - - - - - - - - - - - - - - - - -

    private static BitManager ParseCommon(BitManager bm, ulong value)
    {
        for (int i = 0; i < bm.BitLength; i++)
        {
            ulong mask = 1UL << i;
            if (mask > value)
            {
                break;
            }

            bool bit = (value & mask) != 0;
            bm[i] = bit;
        }
        return bm;
    }

    private ulong ToValue(BitSize size)
    {
        ulong value = 0;
        for (int i = 0; i < size.Value; i++)
        {
            if (bits[i] == false)
            {
                continue;
            }

            value += 1UL << i;
        }
        return value;
    }
}

ビット操作は bool を int で受け付けています。一部効率の悪い処理が含まれるのは把握しています。処理効率を高めたい場合は必要に応じて各自コードを修正した方がいいかもしれません。

以上です。