ArraySegment<T>構造体を使うと、オリジナルの配列の一部分(区間)を参照して部分配列を構成することができます。 例えば、配列arr
の要素10番目から5つ分を切り出して使いたいといった場合に、ArraySegment構造体を用いることができます。
ArraySegmentを使うと、配列の一部分をコピーして別の配列に格納する、といった操作を省略あるいは簡略化することができます。 ArraySegment構造体はジェネリック構造体であるため、任意の型の配列に対して用いることができます。 ArraySegment構造体はオリジナルの配列を切り出したコピーを作成するものではなく、オリジナルの配列に対するビューとして動作します。
.NET Frameworkでは、Array.Sliceといった配列の一部分をコピーして部分配列を作成するようなメソッドは用意されていないため、Array.Copyメソッドを使って実装するなどする必要があります。
ここでは、オリジナルの配列のビューとして機能する部分配列を構成するArraySegment構造体についてと、オリジナルの配列をコピーして部分配列を作成する方法について解説します。
ArraySegment構造体
概略
PerlやRubyなどの言語では、配列から部分配列を切り出すための以下のような構文が用意されています。
C#やVBなどの言語では部分配列を切り出すような構文は用意されていませんが、ArraySegment構造体を用いるとこれに似た操作を行うことができます。
部分配列の操作
ArraySegment構造体では、配列と同様にforeach
による要素の列挙を行うことができます。
ArraySegment構造体はインデクサを直接サポートしないので、インデックスを使ったアクセスはできません。 ArraySegmentでインデックスを使った要素へのアクセスを行いたい場合は、ArraySegmentをIReadOnlyList<T>インターフェイス(System.Collections.Generic名前空間)にキャストして用います。 IReadOnlyList<T>にキャストした場合も、foreach
によって列挙することができます。
インデクサの仕組みについてはプロパティ §.インデクサを参照してください。
このほかにも、ArraySegment構造体はIEnumerable<T>やIReadOnlyList<T>をはじめとしてコレクションとしてのインターフェイスを複数実装しているので、配列や他のコレクションと同様に扱うことができます。
また、LINQのメソッドも配列同様に用いることができます。
ArraySegmentは配列の一部分をコピーして切り出すものではなく、配列の一部分を参照するビューとして動作します。 したがって、オリジナルの配列に対する変更は、ArraySegmentにも影響します。 また、その逆も同様に影響します。
このようにオリジナルの配列を参照する部分配列ではなく、オリジナルをコピーして互いに独立した部分配列を作成したい場合は、§.コピーによる部分配列の作成で後述するような方法を使用します。
.NET Framework 4以前
.NET Framework 4以前のArraySegmentは、IEnumerable<T>やIList<T>などのインターフェイスが一切実装されておらず、配列の区間を定義する程度のごく限定された機能しか持ちません。 (これらのインターフェイスが実装されるのは.NET Framework 4.5以降です。)
したがって、直接列挙したり、インデックスを指定して参照したり、ここまでで述べたような操作を行うことはできません。 .NET Framework 4以前では、以下のプロパティを参照して部分配列を扱うことになります。
- Arrayプロパティ
- 部分配列として参照される配列(オリジナルの配列)。
- Offsetプロパティ
- 部分配列の最初のインデックス。
- Countプロパティ
- 部分配列内の要素の数。
ArraySegmentが用いられるクラス
.NET Frameworkにおいては、引数などでArraySegmentを用いるクラスはそう多くありません。
- Socket.Sendメソッド・Socket.Receiveメソッドでは、送受信バッファを格納する目的でArraySegment<byte>を用いることができます。
- WebSocket.SendAsyncメソッド・WebSocket.ReceiveAsyncメソッドも同様に、送受信バッファを格納する目的で用いられます。
- .NET Framework 4.6以降では、MemoryStream.TryGetBufferメソッドが取得した内部バッファを格納する目的でArraySegment<byte>を要求します。
Stream.ReadメソッドやStream.Writeメソッドなど、引数でarray, offset, count
のように配列の一部分を表すオフセットと長さを指定するメソッドの方が現状では一般的です。
このような引数を要求するメソッドを作成する場合、オーバーロードのひとつとしてArraySegmentを引数にとるバージョンも用意しておくことにより、利便性を向上させることができます。
コピーによる部分配列の作成
JavaScriptなどの言語では、配列の一部分をコピーして部分配列を切り出すための以下のようなメソッドが用意されています。
Array.Slice
.NET FrameworkのArrayクラスでは、Array.Sliceのようなメソッドが用意されていないため、次の例のようにArray.Copyメソッドを使って独自に実装する必要があります。
この例では、配列型にSlice
という拡張メソッドを追加することにより、配列から直接呼び出せるようなメソッドを作成しています。 拡張メソッドについての詳細は拡張メソッドを参照してください。
この例で用いているArray.Copyでは、各要素の簡易コピーが作成されます。 これは、参照型では参照のみがコピーされ、インスタンスのコピーが作成されるわけではないことを意味します。 簡易コピーの動作や詳細コピーの方法などについてはオブジェクトの複製を参照してください。
Listクラスでは、List.GetRangeメソッドによってListの一部分をコピーして取得することができます。
Skipメソッド・Takeメソッド
配列として取得する必要がなく、単なる列挙や他のコレクションへの格納、LINQでの操作を目的とする場合は、LINQのメソッドSkipとTakeを使うことでも部分配列を切り出すことができます。
さらに、ToArrayメソッドを続けて呼びだせば、Skipメソッド・Takeメソッドの結果を配列として取得することもできます。
上記の例において、ToArrayメソッドの代わりにToListメソッドを用いれば、結果をList<T>として取得することができます。