1. 前置き
実際のコードを見た方が手っ取り早いので、随時Reference SourceのMemoryStreamのソースを参照していきます。
MemoryStreamはメモリ上の領域をバッキングストアとしたStreamで、そのためのバイト配列を内部に持っています。それがプライベートフィールドの_bufferです。その中の実際のデータの位置は_originと_lengthで示されます。つまり、_bufferの全部がデータだとは限らず、その一部に格納される構造になっています。
2. MemoryStream.ToArrayメソッド
MemoryStreamに何らかのデータを詰めたとして、それをStreamとして受け渡すのではなく、直接取り出したい場合、オーソドックスなのはMemoryStream.ToArrayメソッドです。
例えば、こんな感じ。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public string GetSerializedString0<T>(T source) | |
{ | |
using (var ms = new MemoryStream()) | |
{ | |
// Fill the MemoryStream with some data (example). | |
var serializer = new DataContractSerializer(typeof(T)); | |
serializer.WriteObject(ms, source); | |
// MemoryStream.ToArray method makes a copy of inner byte array and | |
// returns the copy. This copying is not always desirable. | |
return Encoding.UTF8.GetString(ms.ToArray()); | |
} | |
} |
このToArrayメソッドのソースを見ると、データの長さ(_length - _origin)と同じ長さのバイト配列を用意し、これにBuffer.InternalBlockCopyメソッド(たぶんBuffer.BlockCopyと同じ)で_buffer中のデータをコピーした後、この配列を返しています。
これは安全な方法ですが、処理の最後にデータを取り出したいだけの場合にはコピーが無駄だったりします。
3. MemoryStream.GetBufferメソッド
これが何とかならないかなと思っていたとき、ヒントをいただいた気がしたのでよく調べたらMemoryStream.GetBufferメソッドが存在しました。このGetBufferメソッドのソースを見ると一目瞭然ですが、_bufferの参照をそのまま返しています。ただし、この中のデータの位置は自分で指定する必要があります。
例えば、開始位置を0に決め打ちすれば、こんな感じになります。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public string GetSerializedString1<T>(T source) | |
{ | |
using (var ms = new MemoryStream()) | |
{ | |
// Fill the MemoryStream with some data (example). | |
var serializer = new DataContractSerializer(typeof(T)); | |
serializer.WriteObject(ms, source); | |
// MemoryStream.GetBuffer method allows access to inner byte array but | |
// the first index and the length must be specified. The first index | |
// will be 0 in many cases but it is not guaranteed. | |
return Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length); | |
} | |
} |
一応、この例のようにMemoryStreamのコンストラクターのうち元となるバイト配列のないオーバーロードを使ったときは、_originは0になるようなので、そこに注意すれば問題はなさそうですが、一抹の不安が残ります。
また、publiclyVisibleを指定できるコンストラクターでこれをfalseにした場合(_exposableがfalseの場合)、GetBufferメソッドは例外を吐きます。
4. MemoryStream.TryGetBufferメソッド
ではどうするかというところで、メソッド群を見直すとMemoryStream.TryGetBufferメソッドが存在しました。これはoutでArraySegment<byte>を返します。
このTryGetBufferメソッドのソースを見ると、ArraySegmentのコンストラクターのoffsetに_originの値を、countに_length - _originの値を指定しています。したがって、返されたArraySegmentのOffsetとCountプロパティでデータの位置を確定できるわけです。
これを使うと以下のようにできます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public string GetSerializedString2<T>(T source) | |
{ | |
using (var ms = new MemoryStream()) | |
{ | |
// Fill the MemoryStream with some data (example). | |
var serializer = new DataContractSerializer(typeof(T)); | |
serializer.WriteObject(ms, source); | |
// MemoryStream.TryGetBuffer method gives a ArraySegment the reference | |
// to inner byte array, the first index and the length. | |
ArraySegment<byte> buff; | |
if (!ms.TryGetBuffer(out buff)) | |
return null; | |
// The first index is provided by the ArraySegment. | |
return Encoding.UTF8.GetString(buff.Array, buff.Offset, buff.Count); | |
} | |
} |
5. まとめ
MemoryStreamの振る舞いはどのコンストラクターでどのように指定するかで細かく変わってくるので、GetBufferで済むか、TryGetBufferで安全策を取るか、別にToArrayで構わないか、結局はケースバイケースなのですが、どんなオプションがあるか知っておくと少し安心できます。
0 コメント :
コメントを投稿