はじめに
今回はBitConverter.GetBytes
とBitConverter.TryWriteBytes
に焦点をあててAllocation
について書きたいと思います。
概要
指定したデータ(例. int
, ulong
)をバイト配列に変換する方法としてBitConverter.GetBytes
が挙げられます。
public static byte[] GetBytes (bool value); public static byte[] GetBytes (char value); public static byte[] GetBytes (double value); public static byte[] GetBytes (Half value); public static byte[] GetBytes (short value); public static byte[] GetBytes (int value); public static byte[] GetBytes (long value); public static byte[] GetBytes (float value); public static byte[] GetBytes (ushort value); public static byte[] GetBytes (uint value); public static byte[] GetBytes (ulong value);
BitConverter.GetBytes メソッド (System) | Microsoft Learn
// 利用例 ulong x = 10; byte[] bytes = BitConverter.GetBytes(x);
とても直感的ではありますが大きな欠点があり、メソッドを呼び出すたびにbyte[]
が生成されてしまいます。Unityで言うところのGC.Alloc
というやつで、マネージドヒープにメモリが確保されるのでガベージコレクションの対象になってしまうやつですね。
一度だけBitConverter.GetBytes
を呼ぶならしょうがないですが、複数回呼び出されるとなるとチリツモでどうにかしたいものです。
BitConverter.TryWriteBytesを利用する
一度確保したbyte[]
を使い回せば、毎回byte[]
が確保されないわけですが、それを実現するためにメソッドを用意してくれています。
public static bool TryWriteBytes (Span<byte> destination, bool value); public static bool TryWriteBytes (Span<byte> destination, char value); public static bool TryWriteBytes (Span<byte> destination, double value); public static bool TryWriteBytes (Span<byte> destination, Half value); public static bool TryWriteBytes (Span<byte> destination, short value); public static bool TryWriteBytes (Span<byte> destination, int value); public static bool TryWriteBytes (Span<byte> destination, long value); public static bool TryWriteBytes (Span<byte> destination, float value); public static bool TryWriteBytes (Span<byte> destination, ushort value); public static bool TryWriteBytes (Span<byte> destination, uint value); public static bool TryWriteBytes (Span<byte> destination, ulong value);
BitConverter.TryWriteBytes メソッド (System) | Microsoft Learn
destination
で渡したSpan
にデータを書き込んでくれるというわけです。また第2引数の型のバイト数以上にdestination
のサイズがなければfalse
が返ってきます。
Span
にあまり馴染みがない人はよければこちらを一読ください。
learn.microsoft.com
利用サンプル1
使い回すようなコードを書いてみます。意味は特にないです。
private void Start() { Span<byte> tmp = new byte[4].AsSpan(); // tmpに32を書き込む BitConverter.TryWriteBytes(tmp, 32); // tmpに12を書き込む BitConverter.TryWriteBytes(tmp, 12); // tmpに51を書き込む BitConverter.TryWriteBytes(tmp, 51); }
ちなみに明示的にSpan
にしなくてもキャストしてくれます。
byte[] tmp = new byte[4]; // tmpに32を書き込む BitConverter.TryWriteBytes(tmp, 32);
また配列の確保するサイズが小さいと返り値がfalse
になります。
byte[] tmp = new byte[2]; // 32はintなので、byte[4]が必要 var flag = BitConverter.TryWriteBytes(tmp, 32); // false (byte[4]ならtrue) Debug.Log(flag);
利用サンプル2
例えばbyte[32]
に対して、intを8つ連続して書き込むみたいな処理を書いてみたいと思います。
private void Start() { const int byteSize = 32; var bytes = new byte[byteSize]; var random = new Random(); // random.NextBytes(bytes) のようなイメージ // intは4バイトなので、bytesに8つのulongを書き込んでいく for (var i = 0; i < byteSize / sizeof(int); i++) { var isSucceeded = BitConverter.TryWriteBytes(bytes.AsSpan(sizeof(int) * i, sizeof(int)), random.Next()); // 変換が成功した場合はtrue, それ以外の場合はfalse Debug.Log(isSucceeded); } // 199,152,208,125,37,75,41,102,217,147,44,11,54,80,84,82,230,112,217,22,174,124,136,39,222,27,173,89,220,55,13,40 Debug.Log(string.Join(",", bytes)); }