Help us understand the problem. What is going on with this article?

(メモ)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を作成できそう。ネットで実装方法を何通りか調べてどれがいいのかとか調べる(理解に時間をかける)前に、まずこの方法を選択する価値はあるかもしれません。
MsCorelib.dllのライセンスでBitStreamReaderの改変&公開可能か不明なため上記対応の実装例の記載は控えます。

longlongago_k
コピペですぐ動かせるレベルのtipsをまとめる
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away