C#

(メモ)BitConverterのBigEndian対応

More than 1 year has passed since last update.

C#のBitConverterとエンディアン

byte配列からプリミティブ型(int, short等)を切り出すとき、BitConverterを使用しますが、BitConverterリトルエンディアンで動作します。ここで、ビッグエンディアンのときはBitConverterは使用できません。
インターネットで調べると大体以下の結論となっています。

  • BitConverter.IsLittleEndianがあり、ここを変えればビッグエンディアンで動きそうだが、IsLittleEndianはreadonlyなのでどうしようもない。
  • したがって、自分で実装しないといけない。

関連記事:
エンディアン指定が必要な場合のBitConverterの書き方
http://schima.hatenablog.com/entry/20110301/1299001406
Union使用版
http://d.hatena.ne.jp/finger_works/20120726/1343307427

私は以下のようにビット反転マクロを作成していました。

public static ushort ENDIAN(ushort val)
{
  return (ushort)((int)(val & 255) << 8 | (val & 65280) >> 8);
}

ここで、MSの実装はどうなっているのかとmscorelibの公開コードを見てみると、
http://referencesource.microsoft.com/mscorlib/system/bitconverter.cs.html#mscorlib/system/bitconverter.cs,8640d8adfffb155b

public static unsafe short ToInt16(byte[] value, int startIndex) {
  /*~エラーチェックとかのコード、省略~*/

  fixed( byte * pbyte = &value[startIndex]) {
    if( startIndex % 2 == 0) { // data is aligned 
      return *((short *) pbyte);
    }
    else {
      if( IsLittleEndian) { 
        return (short)((*pbyte) | (*(pbyte + 1) << 8)) ;
      }
      else {
        return (short)((*pbyte << 8) | (*(pbyte + 1)));  //<--これは!!!!!
      }
    }
  }
}

...ビッグエンディアン用のコード入ってるじゃん。しかし、このIsLittleEndianは最初に書いたとおりReadOnlyです。だた、ここまでのコードが書いてあれば、ほとんど手を入れずにビッグエンディアン対応が実現できそうです。パフォーマンスもMSのBitConverter相当ですね。
ただし、気を付けないといけないのは、fixedすぐあとの最初のifです。このコードはリトルエンディアンでしか動作しないので、リトルエンディアンのスコープへ移動させる必要があります。

public static unsafe short ToInt16(byte[] value, int startIndex) {
  /*~エラーチェックとかのコード、省略~*/

  fixed( byte * pbyte = &value[startIndex]) {
    if( IsLittleEndian) { //自作クラスでIsLittleEndianを変更可能にする、または引数で指定できるようにする。
      if( startIndex % 2 == 0) { // data is aligned 
        return *((short *) pbyte); //この行はリトルエンディアンでしか使用できない。
      }
      else {
        return (short)((*pbyte) | (*(pbyte + 1) << 8)) ;
      }
    }
    else {
      return (short)((*pbyte << 8) | (*(pbyte + 1)));
    }
  }
}

また、VS用のパラメータヒントなどで使用されるAttributeですが、MSのページには載っていないので、mscorelibをILSpyで見ると、BitConverterのToInt*は以下のようになっています。

        /// <summary>バイト配列内の指定位置にある 2 バイトから変換された 16 ビット符号なし整数を返します。</summary>
        /// <returns>
        ///   <paramref name="startIndex" /> から始まる 2 バイトで構成される 16 ビット符号なし整数。</returns>
        /// <param name="value">バイト配列。</param>
        /// <param name="startIndex">
        ///   <paramref name="value" /> 内の開始位置。</param>
        /// <exception cref="T:System.ArgumentException">
        ///   <paramref name="startIndex" /> が、<paramref name="value" /> の長さから 1 を引いた値と同じです。</exception>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="value" /> が null です。</exception>
        /// <exception cref="T:System.ArgumentOutOfRangeException">
        ///   <paramref name="startIndex" /> が 0 未満か、<paramref name="value" /> の長さから 1 を引いた値より大きい値です。</exception>
        /// <filterpriority>1</filterpriority>
        [__DynamicallyInvokable, CLSCompliant(false)]
        public static ushort ToUInt16(byte[] value, int startIndex)

これも、クラスを自作したときに流用できそうです。

まとめ

BitConverterのMS実装にビッグエンディアン用のコードが入っているので、ここを流用すると簡単にビッグエンディアン対応版BitConverterを作成できそう。ネットで実装方法を何通りか調べてどれがいいのかとか調べる(理解に時間をかける)前に、まずこの方法を選択する価値はあるかもしれません。