ArrayクラスのCloneメソッドを使い、配列をコピー(シャローコピー)する方法を説明する。また、拡張メソッドを使いキャストを抑制する方法も取り上げる。
配列の複製を作るのは、Arrayクラス(System名前空間)のCopyメソッドや配列のCopyToメソッドを使ってもよいが、配列のCloneメソッドを使えば簡単に書ける。
ただし、上記のいずれにしてもシャローコピーであるので、注意してほしい(後述)。
なお、配列は.NET Frameworkの最初からあるものだが、本稿はそれ以降の内容も含んでいる。サンプルコードをそのまま試すには、Visual Studio 2015(またはそれ以降)が必要である。
配列のCloneメソッドを使い、その結果をキャストすればよい。
コード量は増えるが、ArrayクラスのCopyメソッドでも可能である。両方を記述したコンソールアプリの例を次のコードに示す。Cloneメソッドでは複製後にキャストが必要となる一方で、Copyメソッドではあらかじめ複製先の配列の用意が必要という違いがある。
using System;
using static System.Console;
class Program
{
// 配列の内容をコンソールに出力するメソッド
static void Display2dArray(string title, string[,] array)
{
for (int i = 0; i < array.GetLength(0); i++)
{
Write($"{title}[{i},*] = ");
for (int j = 0; j < array.GetLength(1); j++)
Write($"{array[i, j]}, ");
WriteLine();
}
}
static void Main(string[] args)
{
// 複製元の2次元配列
string[,] src = {
{"abc","def","ghi", },
{"123","456","789", },
};
Display2dArray("src", src);
// 出力:
// src[0,*] = abc, def, ghi,
// src[1,*] = 123, 456, 789,
// ArrayクラスのCopyメソッドを使う
string[,] clone1 = new string[src.GetLength(0), src.GetLength(1)];
Array.Copy(src, clone1, src.Length);
Display2dArray("clone1", clone1);
// 出力:
// clone1[0,*] = abc, def, ghi,
// clone1[1,*] = 123, 456, 789,
// 配列のCloneメソッドなら1行で書ける
string[,] clone2 = src.Clone() as string[,];
Display2dArray("clone2", clone2);
// 出力:
// clone2[0,*] = abc, def, ghi,
// clone2[1,*] = 123, 456, 789,
#if DEBUG
ReadKey();
#endif
}
}
Imports System.Console
Module Module1
' 配列の内容をコンソールに出力するメソッド
Sub Display2dArray(title As String, array(,) As String)
For i As Integer = 0 To (array.GetLength(0) - 1)
Write($"{title}({i},*) = ")
For j As Integer = 0 To (array.GetLength(1) - 1)
Write($"{array(i, j)}, ")
Next
WriteLine()
Next
End Sub
Sub Main()
' 複製元の2次元配列
Dim src(,) As String = {
{"abc", "def", "ghi"},
{"123", "456", "789"}
}
Display2dArray("src", src)
' 出力:
' src(0,*) = abc, def, ghi,
' src(1,*) = 123, 456, 789,
' ArrayクラスのCopyメソッドを使う
Dim clone1(src.GetLength(0) - 1, src.GetLength(1) - 1) As String
Array.Copy(src, clone1, src.Length)
Display2dArray("clone1", clone1)
' 出力:
' clone1(0,*) = abc, def, ghi,
' clone1(1,*) = 123, 456, 789,
' 配列のCloneメソッドなら1行で書ける
Dim clone2(,) As String = CType(src.Clone(), String(,))
Display2dArray("clone2", clone2)
' 出力:
' clone2(0,*) = abc, def, ghi,
' clone2(1,*) = 123, 456, 789,
#If DEBUG Then
ReadKey()
#End If
End Sub
End Module
Cloneメソッドで複製するときにはキャストしなければならない。複製するコードをたくさん書くときは、次のコードのような拡張メソッドを作っておくとキャストをいちいち書かずに済むので便利である。
static class CloneableExtension
{
public static T CloneEx<T>(this T src) where T: class, ICloneable
=> src.Clone() as T;
}
……省略……
// 拡張メソッドを作れば、クローンするところではキャスト不要
string[,] clone3 = src.CloneEx();
Display2dArray("clone3", clone3);
// 出力:
// clone3[0,*] = abc, def, ghi,
// clone3[1,*] = 123, 456, 789,
Public Module CloneableExtension
<Runtime.CompilerServices.Extension()>
Public Function CloneEx(Of T As {Class, ICloneable})(src As T) As T
Return src.Clone()
End Function
End Module
……省略……
' 拡張メソッドを作れば、クローンするところではキャスト不要
Dim clone3(,) As String = src.CloneEx()
Display2dArray("clone3", clone3)
' 出力:
' clone3(0,*) = abc, def, ghi,
' clone3(1,*) = 123, 456, 789,
なお、この拡張メソッドは、配列だけでなく、ICloneableインタフェースを実装しているクラスならどれでも使える。
シャローコピーとは、参照だけを複製するという意味だ。参照先のオブジェクトも複製する場合は、ディープコピーという。
ArrayクラスのCopyメソッドや配列のCloneメソッドは、シャローコピーを行う。配列の内容にオブジェクトへの参照が入っている場合は、オブジェクトそのものの複製は作られないのである。すなわち、複製を作った後で元の配列の参照先のオブジェクトに変更を加えると、複製側も変わるのだ。
Cloneメソッドがそのようなシャローコピーを行うことは、次のコードで確かめられる。複製後に複製元の参照するオブジェクトに変更を加えると、複製した配列の方も変わっている(コード末尾に確認として載せたが、複製元のオブジェクトを入れ替えた場合には複製先に影響しない)。
// 配列に格納するクラスの例
public class SampleData
{
public int Index { get; set; }
public string Name { get; set; }
public override string ToString() => $"{Index:00}:{Name}";
}
……省略……
// 複製元の配列
SampleData[] src = {
new SampleData {Index=1, Name="aaa" },
new SampleData {Index=2, Name="bbb" },
new SampleData {Index=3, Name="ccc" },
};
// 複製を作る
var clone = src.Clone() as SampleData[];
// ソースコード冒頭にusing System.Linq;が必要
WriteLine($"{string.Join(", ", clone.Select(s => s.ToString()))}");
// 出力:01:aaa, 02:bbb, 03:ccc
// srcの2番目のNameプロパティを変更する
src[1].Name = "ZZZ";
// すると、cloneの方も変わっている
WriteLine($"{string.Join(", ", clone.Select(s => s.ToString()))}");
// 出力:01:aaa, 02:ZZZ, 03:ccc
// 確認:srcの3番目に別のオブジェクトを代入=参照そのものを入れ替える
src[2] = new SampleData { Index = 4, Name = "DDD" };
// この場合は、cloneの参照先は変わらない
WriteLine($"{string.Join(", ", clone.Select(s => s.ToString()))}");
// 出力:01:aaa, 02:ZZZ, 03:ccc
' 配列に格納するクラスの例
Public Class SampleData
Public Property Index As Integer
Public Property Name As String
Public Overrides Function ToString() As String
Return $"{Index:00}:{Name}"
End Function
End Class
……省略……
' 複製元の配列
Dim src() As SampleData = {
New SampleData With {.Index = 1, .Name = "aaa"},
New SampleData With {.Index = 2, .Name = "bbb"},
New SampleData With {.Index = 3, .Name = "ccc"}
}
' 複製を作る
Dim clone = CType(src.Clone(), SampleData())
WriteLine($"{String.Join(", ", clone.Select(Function(s) s.ToString()))}")
' 出力:01:aaa, 02:bbb, 03:ccc
' srcの2番目のNameプロパティを変更する
src(1).Name = "ZZZ"
' すると、cloneの方も変わっている
WriteLine($"{String.Join(", ", clone.Select(Function(s) s.ToString()))}")
' 出力:01:aaa, 02:ZZZ, 03:ccc
' 確認:srcの3番目に別のオブジェクトを代入=参照そのものを入れ替える
src(2) = New SampleData With {.Index = 4, .Name = "DDD"}
' この場合は、cloneの参照先は変わらない
WriteLine($"{String.Join(", ", clone.Select(Function(s) s.ToString()))}")
' 出力:01:aaa, 02:ZZZ, 03:ccc
配列の複製を作るには、配列のCloneメソッドを使うと1行で済む。Cloneメソッドによる複製はキャストが必要だが、拡張メソッドを作ればいちいちキャストを書かずに済む。
Cloneメソッドによる複製はシャローコピーである。複製した後で元の配列のオブジェクトに加えた変更は、複製にも反映されるので気を付けよう。
利用可能バージョン:.NET Framework 1.0以降(サンプルコードにはそれ以降の機能/構文も含む)
カテゴリ:クラス・ライブラリ 処理対象:配列
使用ライブラリ:Arrayクラス(System名前空間)
関連TIPS:配列の一部だけをコピーするには?[C#/VB]
関連TIPS:配列のコピーを1行でするには?[C#/VB]
関連TIPS:C#で配列を宣言するには?
関連TIPS:VB.NETで配列を宣言するには?
関連TIPS:配列のサイズを変更するには?(Resize編)[2.0のみ、C#、VB]
関連TIPS:文字列配列内の文字列を連結するには?
関連TIPS:構文:クラス名を書かずに静的メソッドを呼び出すには?[C# 6.0]
関連TIPS:VB.NETでクラス名を省略してメソッドや定数を利用するには?
関連TIPS:数値を右詰めや0埋めで文字列化するには?[C#、VB]
関連TIPS:Visual Studioでコンソール・アプリケーションのデバッグ実行時にコマンド・プロンプトを閉じないようにするには?
Copyright© Digital Advantage Corp. All Rights Reserved.