連載
» 2017年12月27日 05時00分 公開

.NET TIPS:配列を連結するには[C#/VB]

ArrayクラスのCopyメソッド/CopyToメソッド、BufferクラスのBlockCopyメソッド、LINQのConcatメソッドなどを使い、配列を連結する方法を解説する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Development]
「.NET TIPS」のインデックス

連載目次

 2つの配列を連結するには幾つかの方法がある。本稿ではその違いを整理して解説する。

POINT 配列を連結する方法の使い分け

配列を連結する方法の使い分けまとめ 配列を連結する方法の使い分けまとめ


 特定のトピックをすぐに知りたいという方は以下のリンクを活用してほしい。

 なお、利用できる.NET Frameworkのバージョンはさまざまであるが、本稿に掲載したサンプルコードをそのまま試すにはVisual Studio 2015以降が必要である。また、サンプルコードはコンソールアプリの一部であり、コードの冒頭に以下の宣言が必要となる。

using System;
using System.Collections.Generic;
using System.Linq;
using static System.Console;

Imports System.Console

本稿のサンプルコードに必要な宣言(上:C#、下:VB)

 また、サンプルコードでは次のコードに示すメソッドを呼び出している。

static void ShowAll(IEnumerable<int> collection)
  => WriteLine(string.Join(", ", collection.Select(i => i.ToString()).ToArray()));

Sub ShowAll(collection As IEnumerable(Of Integer))
  WriteLine(String.Join(", ", collection.Select(Function(i) i.ToString()).ToArray()))
End Sub

本稿のサンプルコードで配列の出力に使用しているメソッド(上:C#、下:VB)
このメソッドの引数は、整数の配列をIEnumerableインタフェース型(System.Collections.Generic名前空間)のコレクションとして受け取っている。
受け取ったコレクションをLINQを使って文字列の配列に変換し、Stringクラス(System名前空間)のJoinメソッド使って「,」で区切られた1つの文字列に結合し、それをコンソールに出力している。

新しい配列を作ってそこにコピーする(.NET Framework 1.1)

 新しい配列を作り、Arrayクラス(System名前空間)のCopyToメソッド/Copy静的メソッドを使って元の配列の内容をコピーする。.NET Framework 1.1から(すなわち全てのバージョンの.NET Frameworkで)利用できる。配列の内容はどんな型でもよい。

 1次元配列の場合は、CopyToメソッドが使える(次のコード)。後述するCopy静的メソッドよりも引数が少なく、使い勝手がよい。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// 新しい配列を作る
int[] newArray = new int[array1.Length + array2.Length];

// 元の配列の内容を新しい配列へコピーする
array1.CopyTo(newArray, 0);
array2.CopyTo(newArray, array1.Length);
ShowAll(newArray);
// 出力:1, 2, 3, 2, 4, 6

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' 新しい配列を作る
Dim newArray(array1.Length + array2.Length - 1) As Integer

' 元の配列の内容を新しい配列へコピーする
array1.CopyTo(newArray, 0)
array2.CopyTo(newArray, array1.Length)
ShowAll(newArray)
' 出力:1, 2, 3, 2, 4, 6

ArrayクラスのCopyToメソッドを使う例(上:C#、下:VB)
.NET Framework 1.1以降、1次元配列のみ。
CopyToメソッドの第2引数は、コピー先の開始位置(0始まり)である。

 Copy静的メソッドを使えば、多次元配列でも連結できる(次のコード)。

int[,] array21 = { { 1, 2, 3, }, { 4, 5, 6,} };
int[,] array22 = { { 11, 12, 13, }, { 14, 15, 16, } };

// 新しい配列を作る
int[,] newArray2 = new int[4,3];

// 元の配列の内容を新しい配列へコピーする
Array.Copy(array21, newArray2, array21.Length);
Array.Copy(array22, 0, newArray2, array21.Length, array22.Length);
for (int i = 0; i < 4; i++)
{
  for (int j = 0; j < 3; j++)
    Write($"{newArray2[i,j]}, ");
  WriteLine();
}
// 出力:
// 1, 2, 3,
// 4, 5, 6,
// 11, 12, 13,
// 14, 15, 16,

Dim array21(,) As Integer = {{1, 2, 3}, {4, 5, 6}}
Dim array22(,) = {{11, 12, 13}, {14, 15, 16}}

' 新しい配列を作る
Dim newArray2(3, 2) As Integer

' 元の配列の内容を新しい配列へコピーする
Array.Copy(array21, newArray2, array21.Length)
Array.Copy(array22, 0, newArray2, array21.Length, array22.Length)
For i As Integer = 0 To 3
  For j As Integer = 0 To 2
    Write($"{newArray2(i, j)}, ")
  Next
  WriteLine()
Next
' 出力:
' 1, 2, 3,
' 4, 5, 6,
' 11, 12, 13,
' 14, 15, 16,

ArrayクラスのCopy静的メソッドを使う例(上:C#、下:VB)
.NET Framework 1.1以降、多次元配列も可。
Copy静的メソッドのコピーは、多次元配列も1次元配列と見なして行われる。ここではコピー先を4×3の配列としたが、要素数が合っていればよいので、2×6や12×1の配列にもコピーできる。
Copy静的メソッドの引数はちょっと複雑だ。
3引数のもの(コード中の1番目)の第3引数(array21.Length)は、コピーする要素の数である。
5引数のもの(コード中の2番目)では、第2引数(0)はコピー元の開始位置(0始まり)、第4引数(array21.Length)はコピー先の開始位置(0始まり)、そして第5引数(array22.Length)はコピーする要素の数である。

 なお、1次元配列かつその要素がプリミティブ型(数値/真偽値など)の場合には、Bufferクラス(System名前空間)のBlockCopyメソッドを使うと高速にコピーできる(次のコード)。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// 新しい配列を作る
int[] newArray = new int[array1.Length + array2.Length];

// 元の配列の内容を新しい配列へコピーする
int array1Length = Buffer.ByteLength(array1);
int array2Length = Buffer.ByteLength(array2);
Buffer.BlockCopy(array1, 0, newArray, 0, array1Length);
Buffer.BlockCopy(array2, 0, newArray, array1Length, array2Length);
ShowAll(newArray);
// 出力:1, 2, 3, 2, 4, 6

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' 新しい配列を作る
Dim newArray(array1.Length + array2.Length - 1) As Integer

' 元の配列の内容を新しい配列へコピーする
Dim array1Length As Integer = Buffer.ByteLength(array1)
Dim array2Length As Integer = Buffer.ByteLength(array2)
Buffer.BlockCopy(array1, 0, newArray, 0, array1Length)
Buffer.BlockCopy(array2, 0, newArray, array1Length, array2Length)
ShowAll(newArray)
' 出力:1, 2, 3, 2, 4, 6

BufferクラスのBlockCopyメソッドを使って高速にコピーする例(上:C#、下:VB)
.NET Framework 1.1以降、プリミティブ型の1次元配列のみ。
BlockCopyメソッドでは、位置や長さをバイト単位で指定する。
第2引数はコピー元の開始位置(バイト単位で0始まり)、第4引数はコピー先の開始位置(バイト単位で0始まり)、第5引数はコピーするバイト数である(要素数ではない)。そのため、BufferクラスのByteLengthメソッドを使って、コピー元の配列の長さをバイト単位で求めている。

配列を拡大してそこにコピーする(.NET Framework 2.0)

 .NET Framework 2.0からは、ArrayクラスのResizeメソッドで1次元配列のサイズを拡大できる。1つ目の配列を拡大し、そこに2つ目の配列の内容をCopyToメソッドなどを使ってコピーして追加すれば、2つを連結した配列になる(次のコード)。

 ただし、配列を拡大するといっても、内部的には新しい配列を作ってそこにコピーしている。前述した新しい配列を作る方法と、本質的には同じことである。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// 1つ目の配列を拡大する
Array.Resize(ref array1, array1.Length + array2.Length);

// 2つ目の配列の内容を1つ目の配列へコピーする
array2.CopyTo(array1, array1.Length - array2.Length);
ShowAll(array1);
// 出力:1, 2, 3, 2, 4, 6

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' 1つ目の配列を拡大する
Array.Resize(array1, array1.Length + array2.Length)

' 2つ目の配列の内容を1つ目の配列へコピーする
array2.CopyTo(array1, array1.Length - array2.Length)
ShowAll(array1)
' 出力:1, 2, 3, 2, 4, 6

配列を拡大してそこにコピーする例(上:C#、下:VB)
.NET Framework 2.0以降、1次元配列のみ。

 なお、Visual Basicでは、ReDimステートメントでも配列のサイズを変更できる(次のコード)。ReDimステートメントも、内部的には新しい配列を作っている。

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' 1つ目の配列を拡大する
ReDim Preserve array1(array1.Length + array2.Length - 1)

' 2つ目の配列の内容を1つ目の配列へコピーする
array2.CopyTo(array1, array1.Length - array2.Length)
ShowAll(array1)
' 出力:1, 2, 3, 2, 4, 6

ReDimステートメントで配列を拡大してそこにコピーする例(VB)

LINQで連結する(.NET Framework 3.5)

 .NET Framework 3.5で導入されたLINQを使うと、1次元配列の連結がとても簡潔に書ける(次のコード)。メモリ効率も前述の方法とそん色ないので、.NET Framework 3.5以降ではLINQを使うこの方法がお勧めである。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// LINQで連結する
int[] concatenated = array1.Concat(array2).ToArray();
ShowAll(concatenated);
// 出力:1, 2, 3, 2, 4, 6

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' LINQで連結する
Dim concatenated() As Integer = array1.Concat(array2).ToArray()
ShowAll(concatenated)
' 出力:1, 2, 3, 2, 4, 6

LINQのConcat拡張メソッドを使う例(上:C#、下:VB)
.NET Framework 3.5以降、1次元配列のみ。
Concat拡張メソッドはIEnumerable<T>型を返してくるので、ToArray拡張メソッドを使って配列に変換する。Concat拡張メソッドの中では、配列用に新しくメモリを確保することはない。配列用のメモリを確保するのは、ToArray拡張メソッドの中だけである。

 また、LINQでは、重複する要素を除外してマージすることも簡単である(次のコード)。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// LINQで重複を排除して連結する
int[] union = array1.Union(array2).ToArray();
ShowAll(union);
// 出力:1, 2, 3, 4, 6

Dim array1() As Integer = {1, 2, 3}
Dim array2() As Integer = {2, 4, 6}

' LINQで重複を排除して連結する
Dim union() As Integer = array1.Union(array2).ToArray()
ShowAll(union)
' 出力:1, 2, 3, 4, 6

LINQのUnion拡張メソッドを使って重複を排除して連結する例(上:C#、下:VB)
.NET Framework 3.5以降、1次元配列のみ。
出力を見ると、重複していた「2」が1つになっている。

 なお、1つ目の配列からList<T>コレクション(System.Collections.Generic名前空間)を作り、List<T>クラスのAddRangeメソッドを使って2つ目の配列を連結し、再び配列に戻すという方法でも可能ではある。しかし、この方法はコードが長くなる上にメモリを余分に使うので、あまりお勧めではない(次のコード、C#のみで解説)。

int[] array1 = { 1, 2, 3, };
int[] array2 = { 2, 4, 6, };

// 1つ目の配列をLINQでList<T>に変換する
var list1 = array1.ToList();

// [確認]上で作成したList<T>オブジェクトは、元の配列とは別物
list1[0] = 100;
ShowAll(array1); // 出力:1, 2, 3
ShowAll(list1);  // 出力:100, 2, 3
// →array1とlist1は別物。list1の分だけ余計にメモリを確保した

// List<T>オブジェクトの後ろに、2つ目の配列を連結する
list1.AddRange(array2);

// List<T>オブジェクトをLINQで配列に変換する
int[] result = list1.ToArray();

// [確認]上で作成した配列は、List<T>オブジェクトとは別物
list1[0] = -99;
ShowAll(list1);  // 出力:-99, 2, 3, 2, 4, 6
ShowAll(result); // 出力:100, 2, 3, 2, 4, 6
// →list1とresultは別物。resultの分だけメモリをさらに確保した

LINQとList<T>クラスのAddRangeメソッドを使って連結する例(C#)
.NET Framework 3.5以降、1次元配列のみ。
この方法はメモリを余分に使うため、あまりお勧めできない。array1とlist1とresultは、それぞれ個別にメモリを確保している。コメントに「[確認]」とした部分で、それぞれの先頭の要素を書き換えてみて、お互いに影響しないこと(=独立したオブジェクトであること)を確かめている。連結する配列array2を含めて、この方法では4つの独立した配列/コレクションを使っているわけだ。対して、ここまでの方法はいずれも、array1/array2と新しく作った配列の3つだけで済んでいる。

まとめ

 1次元配列を連結するには、.NET Framework 3.5以降ならLINQを使うのが簡潔だ。それ以前では、新しい配列を作って元の配列をコピーするか、あるいは元の配列を拡張するかした後で、連結する配列をその後ろにコピーする。

 プリミティブ型の1次元配列では、BufferクラスのBlockCopyメソッドを使うと高速にコピーできる。

 多次元配列の場合は、新しい配列を作って、ArrayクラスのCopy静的メソッドを使う。

利用可能バージョン:.NET Framework 1.1以降(一部、2.0以降、または3.5以降)
カテゴリ:クラスライブラリ 処理対象:配列
使用ライブラリ:Arrayクラス(System名前空間)
使用ライブラリ:Bufferクラス(System名前空間)
使用ライブラリ:Enumerableクラス(System.Linq名前空間)
関連TIPS:配列のコピーを1行でするには?[C#/VB]
関連TIPS:配列の一部だけをコピーするには?[C#/VB]
関連TIPS:配列のサイズを変更するには?(Resize編)[2.0のみ、C#、VB]
関連TIPS:C#で配列を宣言するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:構文:メソッドやプロパティをラムダ式で簡潔に実装するには?[C# 6.0/7.0]
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2018 Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。