C#::配列の連結
LINQで追加されたIEnumerable用拡張メソッドにConcatというのがあって、これを使うことで二つのシーケンスを連結できる。
byte[] seq1 = new byte[]{1,2,3}; byte[] seq2 = new byte[]{4,5,6}; byte[] seq = seq1.Concat(seq2).toArray();
これで、seq = byte[6]{1,2,3,4,5,6}となる。
ところがこいつは微妙に使い勝手が悪いことに、連結するもの(seq2)がIEnumerableでなければならない。だから、値を一個だけ追加したいときにも配列に入れてから呼び出さなければならない。そして得てして一個ずつ追加することが多い。
stream = BitConverter.GetBytes(25) .Concat(new byte[] { 26 }).ToArray();
これだとあまりにも不恰好なので(ちなみにGetBytesにbyte型変数を突っ込むとushortに拡張されるのかして2byteの配列になります)、拡張メソッドを追加してみる。
public static class IEnumerableExtensions{ public static IEnumerable<T> Concat<T>(this IEnumerable<T> lst, T val){ foreach(var v in lst){ yield return v; } yield return val; } }
ところがどっこい、実際に動かしてみるとこいつはConcat(new byte[] { 26 })に比べて1.5倍程度遅かった。なので
public static class IEnumerableExtensions{ public static IEnumerable<T> Concat<T>(this IEnumerable<T> lst, T val){ return lst.Concat(new T[] { val }); } }
こんな風にしてみた。これで、実行速度はほぼ同等(関数呼び出し一個分ぐらい遅い)のまま、Concatで非IEnumerableデータを連結できるようになりました。
stream = BitConverter.GetBytes(25) .Concat((byte)26).ToArray();
次のようなシーケンス生成関数も試してみた。Concat(((byte)25).Sequence())とやって使うわけだけど、これも微妙に遅い。1.2倍ぐらいの時間がかかる。記述もConcatのオーバーロードに比べて冗長だよね。
public static IEnumerable<T> Sequence<T>(this T t){ yield return t; }
ちなみに。
Concatのすばらしいところは、無限に数珠繋ぎできるところです。実行速度はお察しください?どういう実装になってるんだろう。自分でyield使って似たようなのを書くよりは早いからなにかあるんだろうけど。
stream = BitConverter.GetBytes(25) .Concat(BitConverter.GetBytes(4649)) .Concat((byte)30) .Concat((byte)26) .Concat((byte)22) .Concat((byte)20).ToArray();
まぁ便利。
この処理を使って通信パケットの構築をやったんですけど・・・どうやるのが一番早い(かつ見た目すっきり)のかなぁ。でもunsafeは最後の手段だぜ?
BitConverterにbyte[]とオフセットを受け取って適当に書き換えてくれるメソッドがあればそれが速そうなんだけど。常識的に考えて、yield returnが遅いんだと思うし。
変換用の構造体を作ってMarshal.StructureToPtrを使う方法もあるようですが(id:Schima:20090512:1242139542)、専用の構造体を作るのが無駄だしめんどくさいなぁ。