.NET Frameworkにおける配列はArrayクラスを基本クラスとしていて、すべての配列は暗黙的にこのクラスを継承しています。 配列の基本的な機能はすべてこのクラスにより提供されます。 また、Arrayクラスには様々なメソッドが用意されていて、それらを使うことで言語の構文では用意されていない配列操作などを行うことが出来ます。

ここで解説する配列の操作と、対応するArrayクラスのメソッドを表にまとめます。 個々のメソッドの詳細については順に解説していきます。

配列操作と対応するArrayクラスのメソッド
配列操作 対応するArrayクラスのメソッド 解説へのリンク
配列をクリアする Clear 解説へ
配列のサイズ(長さ)を変更する Resize 解説へ
配列を読み取り専用にする AsReadOnly 解説へ
コピー
(複製・複写)
配列の複製を作成する Clone 解説へ
配列の一部・全部を他の配列に複写する Copy, CopyTo 解説へ
並べ替え 配列内の要素を逆順に(リバース)する Reverse 解説へ
配列内の要素をソートする Sort 解説へ
要素の検索 配列内にある要素を検索してインデックスを取得する IndexOf, LastIndexOf, BinarySearch 解説へ
述語(Predicate)を使って配列内の要素を検索する Find, FindLast, FindAll, FindIndex, FindLastIndex,Exists, TrueForAll 解説へ
全要素に対する操作 配列内の全要素を他の型・値に変換する ConvertAll 解説へ
配列内の全要素を列挙する ForEach 解説へ
構文によらない配列の操作を行う CreateInstance, GetValue, SetValue 解説へ
配列操作 対応するArrayクラスのメソッド 解説へのリンク

§1 クリア (Clear)

Array.Clearメソッドを使うことで、配列内の要素をクリアして初期化することが出来ます。 このメソッドを使って配列内をクリアすると、配列内の各要素にデフォルト値が設定されます。 デフォルト値は型によって異なりますが、数値などの型では0や0に相当する値、文字列型を含む参照型ではヌル参照(null/Nothing)となります。

どのような値で初期化されるか、型とデフォルト値の詳細については型の種類・サイズ・精度・値域 §.型のデフォルト値を参照してください。

また、このメソッドは引数としてクリアを開始するインデックスと長さをとるため、配列の一部分だけをクリアすることも出来ます。

1次元配列のクリア
using System;

class Sample {
  static void Main()
  {
    int[] arr = {0, 1, 2, 3, 4};

    // インデックスが3の要素から2個分をクリア
    Array.Clear(arr, 3, 2);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    // 配列の全要素をクリア
    Array.Clear(arr, 0, arr.Length);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 0, 0, 
0, 0, 0, 0, 0, 

なお、Array.Clearメソッドは配列内の要素をクリアするためのもので、配列の長さを0にするものではありません。 配列の長さを0にするにはResizeメソッドを使います。

Array.Clearメソッドは、0を指定したmemset、ZeroMemoryマクロのような動作となります。 Array.Clearメソッドでは配列を0以外の値にクリアする事はできず、またmemsetやFillMemoryマクロのように配列全体に同一の値にセットするようなメソッドもArrayクラスには用意されていないため、0以外の値に初期化したい場合にはfor文等を使って個別に実装する必要があります。

§1.1 ジャグ配列・多次元配列のクリア

Array.Clearメソッドでジャグ配列をクリアする場合は、ジャグ配列の1段目に格納されている配列がクリアされヌル参照(null/Nothing)の状態になります。 ジャグ配列に格納されている各配列はクリアされません。

2段のジャグ配列のクリア
using System;

class Sample {
  static void Main()
  {
    // ジャグ配列に格納する配列
    int[] arr1 = {0, 1, 2, 3, 4};
    int[] arr2 = {5, 6, 7};

    // 2段のジャグ配列
    int[][] jagged = {
      arr1,
      arr2,
    };

    // ジャグ配列をクリア
    Array.Clear(jagged, 0, jagged.Length);

    Console.WriteLine("jagged[0] == null : {0}", jagged[0] == null);
    Console.WriteLine("jagged[1] == null : {0}", jagged[1] == null);

    foreach (int elem in arr1) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
jagged[0] == null : True
jagged[1] == null : True
0, 1, 2, 3, 4, 

一方、Array.Clearメソッドでは多次元配列をクリアすることも出来ますが、その際多次元配列は1次元配列の様に扱われます。 Array.Clearメソッドの引数に指定するインデックスは、多次元配列を1次元配列に展開したときの位置として解釈されるという点に注意が必要です。

2次元配列のクリア
using System;

class Sample {
  static void Main()
  {
    int[,] matrix = {
      {0, 1, 2, 3},
      {4, 5, 6, 7},
      {8, 9, 10, 11},
    };

    // 2次元配列全体の3番目の要素から4個分をクリア
    Array.Clear(matrix, 3, 4);

    for (int d1 = 0; d1 < matrix.GetLength(0); d1++) {
      for (int d2 = 0; d2 < matrix.GetLength(1); d2++) {
        Console.Write("{0}, ", matrix[d1, d2]);
      }
      Console.WriteLine();
    }
  }
}
実行結果
0, 1, 2, 0, 
0, 0, 0, 7, 
8, 9, 10, 11,


§2 リサイズ (Resize)

Array.Resizeメソッドを使うことで、配列の長さを変更することが出来ます。 このメソッドによってリサイズされるというのは見た目の動作で、実際には、変更後のサイズの配列が新たに作成され、その配列に元の配列の内容をコピーしたものが引数で指定した配列と置き換えられるという動作になります。 このメソッドでは、配列の長さを元の長さよりも短くすることも長くすることも可能です。 Array.Resizeメソッドで配列を拡張した場合、拡張した部分にはデフォルト値がセットされます。

配列のリサイズ
using System;

class Sample {
  static void Main()
  {
    int[] arr = {0, 1, 2, 3, 4};

    // 配列の長さを長くする
    Array.Resize(ref arr, 7);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    // 配列の長さを短くする
    Array.Resize(ref arr, 3);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 0, 0, 
0, 1, 2, 

ジャグ配列をリサイズすることも出来ますが、その場合は1段目の長さのみが変更できます。 2段目以降の長さを変更する場合は、格納されている配列に対して個別にArray.Resizeメソッドを呼び出してリサイズする必要があります。

なお、Array.Resizeメソッドでは多次元配列のサイズを変更することは出来ません。

§3 複製・複写

§3.1 複製 (Clone)

Array.Cloneメソッドを使うことで、配列を複製して同じ内容を持った配列を生成することが出来ます。 Array.Cloneメソッドの戻り値はobjectであるため、必要に応じて複製元と同じ型にキャストして使います。

1次元配列の複製
using System;

class Sample {
  static void Main()
  {
    int[] arr1 = {0, 1, 2, 3, 4};
    int[] arr2;

    // arr1を複製してarr2に代入
    arr2 = (int[])arr1.Clone();

    // 複製元と複製後の配列の要素を変更
    arr1[2] = 0;
    arr2[2] = 5;

    // それぞれの配列の内容を表示
    foreach (int elem in arr1) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 0, 3, 4, 
0, 1, 5, 3, 4, 

このメソッドでは簡易コピーが行われるため、参照型を要素に持つ配列では参照のみがコピーされます。 そのため、ジャグ配列の複製を行うと、複製元と複製後のジャグ配列内における2段目以降はどちらも同一のインスタンスを参照する事になります。

Cloneメソッドと簡易コピーについてより詳しくはオブジェクトの複製で解説しています。

多次元配列を複製する場合の結果は1次元配列を複製する場合と同様で、長さ・次元数・格納される要素が同一の多次元配列が生成されます。

2次元配列の複製
using System;

class Sample {
  static void Main()
  {
    int[,] matrix1 = {
      {0, 1, 2, 3},
      {4, 5, 6, 7},
      {8, 9, 10, 11},
    };
    int[,] matrix2;

    // matrix1を複製してmatrix2に代入
    matrix2 = (int[,])matrix1.Clone();

    // 複製元と複製後の配列の要素を変更
    matrix1[0, 0] = 99;
    matrix2[0, 0] = -1;

    // それぞれの配列の内容を表示
    for (int d1 = 0; d1 < matrix1.GetLength(0); d1++) {
      for (int d2 = 0; d2 < matrix1.GetLength(1); d2++) {
        Console.Write("{0}, ", matrix1[d1, d2]);
      }
      Console.WriteLine();
    }
    Console.WriteLine();

    for (int d1 = 0; d1 < matrix2.GetLength(0); d1++) {
      for (int d2 = 0; d2 < matrix2.GetLength(1); d2++) {
        Console.Write("{0}, ", matrix2[d1, d2]);
      }
      Console.WriteLine();
    }
  }
}
実行結果
99, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11, 

-1, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11, 

§3.2 複写 (Copy, CopyTo)

§3.2.1 Copyメソッド

Array.Copyメソッドは、Cloneメソッドとは異なり配列の内容を既に存在する別の配列に複写します。 引数で複写する長さ(要素数)を指定できるので、配列の途中までをコピーするということも出来ます。 当然、複写先の配列は複写元と同じがそれ以上の長さを持っている必要があります。 Array.Copyメソッドも、Array.Cloneメソッドと同様要素の簡易コピーを行います。

1次元配列の複写
using System;

class Sample {
  static void Main()
  {
    // 複写元の配列
    int[] arr1 = {0, 1, 2, 3, 4};

    // 初期化されていない配列を確保
    int[] arr2 = new int[5];

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    // arr1の要素の先頭から3個分をarr2に複写
    Array.Copy(arr1, arr2, 3);

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    // arr1の全要素をarr2に複写
    Array.Copy(arr1, arr2, arr1.Length);

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 0, 0, 0, 0, 
0, 1, 2, 0, 0, 
0, 1, 2, 3, 4, 

配列のバイト表現を別の配列へ転写する目的(バイト列としてのコピー)にはBuffer.BlockCopyメソッドを使うことができます。 詳しくはバイト列操作 §.BlockCopyメソッドを参照してください。


複写する長さだけでなく、複写元と複写先の始点となるインデックスを指定して複写することも出来ます。 Arrayクラスには部分配列を抜き出すArray.Sliceのようなメソッドは用意されていませんが、次の例のように配列の生成と部分配列の複写を行うことで、Array.Slice相当の処理を行うことが出来ます。

部分配列の抽出(slice)
using System;

class Sample {
  static void Main()
  {
    // 複写元の配列
    int[] arr1 = {0, 1, 2, 3, 4};

    // 初期化されていない配列を確保
    int[] arr2 = new int[3];

    // arr1の部分配列(インデックス2から3個分)をarr2のインデックス0以降に複写
    Array.Copy(arr1, 2, arr2, 0, 3);

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
2, 3, 4, 

この方法では部分配列を取得するために新たな配列インスタンスの作成と複写を行うことになりますが、ArraySegmentを使えばインスタンス生成や複写を行わずに部分配列のビューを取得・作成することもできます。


部分配列の取得と同様に、配列の連結を行うArray.Concatのようなメソッドも用意されていませんが、次の例のように連結後の配列の生成と連結元の配列の複写を行うことで、Array.Concat相当の処理を行うことが出来ます。

配列の連結(concat)
using System;

class Sample {
  static void Main()
  {
    // 連結元の配列
    int[] arr1 = {0, 1, 2, 3, 4};
    int[] arr2 = {5, 6, 7};

    // 連結後の配列となる、初期化されていない配列を確保
    int[] arr3 = new int[arr1.Length + arr2.Length];

    // arr1の内容をarr3の先頭に複写
    Array.Copy(arr1, 0, arr3, 0, arr1.Length);

    // arr2の内容をarr3の続きに複写
    Array.Copy(arr2, 0, arr3, arr1.Length, arr2.Length);

    foreach (int elem in arr3) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 5, 6, 7, 

§3.2.2 多次元配列の複写

多次元配列もArray.Copyメソッドで複写できます。 多次元配列を複写する場合は、次元数が同じで複写先の長さが十分であれば、複写元と複写先で次元毎の長さが異なっていても複写することが出来ます。

2次元配列の複写
using System;

class Sample {
  static void Main()
  {
    // 複写元の2次元配列(長さが4×3)
    int[,] matrix1 = {
      {0, 1, 2, 3},
      {4, 5, 6, 7},
      {8, 9, 10, 11},
    };

    // 複写先の2次元配列(長さが3×5)を確保
    int[,] matrix2 = new int[5, 3];

    // 配列を複写
    Array.Copy(matrix1, matrix2, matrix1.Length);

    // それぞれの配列の内容を表示
    Print(matrix1);

    Console.WriteLine();

    Print(matrix2);
  }

  static void Print(int[,] matrix)
  {
    for (int d1 = 0; d1 < matrix.GetLength(0); d1++) {
      for (int d2 = 0; d2 < matrix.GetLength(1); d2++) {
        Console.Write("{0}, ", matrix[d1, d2]);
      }
      Console.WriteLine();
    }
  }
}
実行結果
0, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11, 

0, 1, 2, 
3, 4, 5, 
6, 7, 8, 
9, 10, 11, 
0, 0, 0, 

Copyメソッドでは次元数が異なる配列への複写が出来ないので、そういった場合はfor文・foreach文を使って要素を一つずつコピーする必要があります。

1次元配列から2次元配列へ複写する例
using System;

class Sample {
  static void Main()
  {
    // 複写元の1次元配列(長さが12)
    int[] matrix1 = {
      0, 1, 2, 3,
      4, 5, 6, 7,
      8, 9, 10, 11,
    };

    // 複写先の2次元配列(長さが4×3)を確保
    int[,] matrix2 = new int[3, 4];

    // 配列を複写
    for (int i = 0; i < matrix1.Length; i++) {
      int d1 = i / matrix2.GetLength(1);
      int d2 = i % matrix2.GetLength(1);

      matrix2[d1, d2] = matrix1[i];
    }

    // 結果を表示
    for (int d1 = 0; d1 < matrix2.GetLength(0); d1++) {
      for (int d2 = 0; d2 < matrix2.GetLength(1); d2++) {
        Console.Write("{0}, ", matrix2[d1, d2]);
      }
      Console.WriteLine();
    }
  }
}
実行結果
0, 1, 2, 3, 
4, 5, 6, 7, 
8, 9, 10, 11, 

§3.2.3 同一配列への複写

Array.Copyメソッドでは、複写先を複写元と同一の配列にすることが出来ます。 この際、複写先と複写元の一部が重なっていても問題なく複写されます。

同一配列への複写
using System;

class Sample {
  static void Main()
  {
    // 複写元の配列
    int[] arr = {0, 1, 2, 3, 4};

    // arrの部分配列(インデックス0から3個分)をarr自身のインデックス2以降にコピー
    Array.Copy(arr, 0, arr, 2, 3);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 0, 1, 2, 

このようにArray.Copyメソッドは、memcpyではなくmemmoveに相当する動作となります。

§3.2.4 異なる型の配列への複写

複写元と複写先の型に互換性がある場合、もしくは型が複写元と複写先がどちらもプリミティブ型で複写が拡大変換となる場合に限り、異なる型の配列へ複写することが出来ます。 例えば、次の例のように、ともにプリミティブ型であり拡大変換となるint型配列からlong型配列への複写は正常に行われます。

異なる型の配列への複写
using System;

class Sample {
  static void Main()
  {
    // 複写元の配列 (int型)
    int[] source = {0, 1, 2, 3, 4};

    // 複写先の配列 (long型)
    long[] dest = new long[5];

    // int型の配列をlong型の配列に複写
    Array.Copy(source, dest, 5);

    foreach (int elem in dest) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 

先に挙げた以外の場合、すなわち型に互換性がない、どちらもプリミティブ型でない、型の変換が縮小変換となるのいずれかの場合では、例外ArrayTypeMismatchExceptionがスローされます。

例えば、上記の例を逆にしてlong型配列からint型配列に複写しようとする場合、どちらの型もプリミティブではあってもlongからintへは縮小変換となるため、ArrayTypeMismatchExceptionがスローされます。

型の分類、プリミティブ型については型の種類・サイズ・精度・値域、拡大変換・縮小変換については基本型の型変換で詳しく解説しています。

Buffer.BlockCopyメソッドでは、プリミティブ型であれば縮小変換となるような配列へのコピーも行うことができます。 詳しくはバイト列操作 §.BlockCopyメソッドを参照してください。

§3.2.5 CopyToメソッド

Array.Copyメソッドの他にも、Array.CopyToメソッドを使って複写することも出来ます。 このメソッドはArray.Copyとは異なりインスタンスメソッドです。 また、引数に指定するのは複写先の始点となるインデックスで、常に複写元の配列全体が複写されます。

CopyToメソッドを使った複写
using System;

class Sample {
  static void Main()
  {
    // 複写元の配列
    int[] arr1 = {0, 1, 2, 3, 4};

    // 初期化されていない配列を確保
    int[] arr2 = new int[7];

    // arr1の全要素をarr2のインデックス1以降に複写
    arr1.CopyTo(arr2, 1);

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 0, 1, 2, 3, 4, 0, 

CopyToはインスタンスメソッドであるという点以外は、Copyメソッドと同様で、その動作もCopyメソッドと同じです。

§4 配列の作成・要素の取得と設定 (CreateInstance, GetValue, SetValue)

言語で用意されている構文を使う他に、Array.CreateInstanceメソッドを使うことで任意の型・任意の長さの配列を作成することが出来ます。 このメソッドの戻り値はArrayクラスとなるので、必要に応じて適切な配列型にキャストしてから使います。 このメソッドで作成した配列は、通常の構文で作成した配列と何ら変わりありません。

また、Array.GetValueメソッドおよびArray.SetValueメソッドを使うことで、配列の各要素にアクセスするための構文(インデクサ)を使用せずに配列に値を取得・設定することが出来ます。 このメソッドは配列の型・次元がどのようなものでも値を取得・設定することが出来ます。

Arrayクラスのメソッドを使った配列の作成と要素の取得・設定
using System;

class Sample {
  static void Main()
  {
    // 型がintで長さが5の配列を作成
    int[] arr1 = (int[])Array.CreateInstance(typeof(int), 5);

    arr1[0] = 0;
    arr1[1] = 1;
    arr1[2] = 2;
    arr1[3] = 3;
    arr1[4] = 4;

    foreach (int elem in arr1) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
    Console.WriteLine();

    // 型がstringで長さが3の配列を作成
    Array arr2 = Array.CreateInstance(typeof(string), 3);

    // SetValueメソッドを使って値を設定
    arr2.SetValue("Alice", 0);    // 配列のインデックス0に文字列"Alice"を設定
    arr2.SetValue("Bob", 1);      // 配列のインデックス1に文字列"Bob"を設定
    arr2.SetValue("Charlie", 2);  // 配列のインデックス2に文字列"Charlie"を設定

    // GetValueメソッドを使って値を取得
    for (int i = 0; i < arr2.Length; i++) {
      Console.WriteLine("arr2[{0}] = {1}", i, arr2.GetValue(i));
    }
  }
}
実行結果
0, 1, 2, 3, 4, 

arr2(0) = Alice
arr2(1) = Bob
arr2(2) = Charlie

GetValueメソッド・SetValueメソッドは要素単位で値の取得・設定を行います。 バイト単位で配列内の値を取得・設定したい場合はBuffer.GetByteメソッドBuffer.SetByteメソッドを使うことができます。 詳しくはバイト列操作 §.バイト単位での値の設定・取得 (GetByteメソッド・SetByteメソッド)を参照してください。


Array.CreateInstance, Array.GetValue, Array.SetValueの各メソッドは、多次元配列でも使うことが出来ます。 次の例では、Array.CreateInstanceメソッドで指定された型の行列を表す2次元配列を作成し、Array.SetValueメソッドで値を設定して単位行列を構成しています。

Arrayクラスのメソッドを使った多次元配列の作成と要素への値の設定
using System;

class Sample {
  // 任意の長さ・型の単位行列を生成する
  static Array CreateIdentityMatrix(int length, Type typeOfMatrix)
  {
    // 指定された型でlength×length行列となる2次元配列を作成
    Array matrix = Array.CreateInstance(typeOfMatrix, length, length);

    // 行列の対角成分に1、それ以外を0に設定する
    for (int i = 0; i < length; i++) {
      for (int j = 0; j < length; j++) {
        if (i == j)
          matrix.SetValue(1, i, j);
        else
          matrix.SetValue(0, i, j);
      }
    }

    return matrix;
  }

  // 行列を表示する
  static void PrintMatrix(Array matrix)
  {
    for (int i = 0; i < matrix.GetLength(1); i++) {
      Console.Write("(");
      for (int j = 0; j < matrix.GetLength(0); j++) {
        Console.Write("{0,-5} ", matrix.GetValue(i, j));
      }
      Console.WriteLine(")");
    }
  }

  static void Main()
  {
    // int型で2行2列の単位行列を作成
    int[,] matrix1 = (int[,])CreateIdentityMatrix(2, typeof(int));

    PrintMatrix(matrix1);
    Console.WriteLine();

    // float型で4行4列の単位行列を作成
    float[,] matrix2 = (float[,])CreateIdentityMatrix(4, typeof(float));

    // 行列内の各要素をスケーリング
    for (int i = 0; i < 4; i++) {
      for (int j = 0; j < 4; j++) {
        matrix2[i, j] *= 0.25f;
      }
    }

    PrintMatrix(matrix2);
    Console.WriteLine();
  }
}
実行結果
(1     0     )
(0     1     )

(0.25  0     0     0     )
(0     0.25  0     0     )
(0     0     0.25  0     )
(0     0     0     0.25  )

§5 読み取り専用化 (AsReadOnly)

Array.AsReadOnlyメソッドを使うことで、配列を元に読み取り専用のコレクションを作成することが出来ます。 このメソッドの戻り値はReadOnlyCollectionで、これは読み取り専用にした配列のように振る舞います。 例えば、インデックスを指定して要素に値を設定しようとするとコンパイルエラーとなり、IList等のインターフェイスにキャストして無理やり変更しようとしてもNotSupportedExceptionがスローされます。 それ以外の操作、例えば要素の参照や値の列挙は、通常の配列と同様に振る舞います。 なお、通常の配列と異なりReadOnlyCollectionにはLengthプロパティが無いので、代わりにCountプロパティを参照します。

配列を読み取り専用化する
using System;
using System.Collections.ObjectModel;

class Sample {
  static void Main()
  {
    int[] source = {0, 1, 2, 3, 4};

    // 配列を読み取り専用にする
    ReadOnlyCollection<int> arr = Array.AsReadOnly(source);

    // 要素を列挙
    for (int i = 0; i < arr.Count; i++) {
      Console.Write("{0}, ", arr[i]);
    }
    Console.WriteLine();

    // 読み取り専用のため、要素の設定は出来ない
    // sample.cs(20,5): error CS0200: プロパティまたはインデクサー 'System.Collections.ObjectModel.ReadOnlyCollection<int>.this[int]' は読み取り専用なので、割り当てることはできません。
    //arr[1] = 5;

    // IListインターフェイスを経由して、無理やり要素を変更しようとする
    (arr as System.Collections.IList)[0] = 5;
  }
}
実行結果
0, 1, 2, 3, 4,

ハンドルされていない例外: System.NotSupportedException: コレクションは読み取り専用です。
   場所 System.Collections.ObjectModel.ReadOnlyCollection`1.System.Collections.IList.set_Item(Int32 index, Object value)
   場所 Sample.Main()

Array.AsReadOnlyメソッドは読み取り専用のラッパーを作成するだけであり、元となった配列が読み取り専用となるのではないため値を設定することが可能です。 元の配列を変更すると、作成したReadOnlyCollectionにもその変更が反映されます。

読み取り専用化の元になった配列に対して変更を加えた場合
using System;
using System.Collections.ObjectModel;

class Sample {
  static void Main()
  {
    int[] source = {0, 1, 2, 3, 4};

    // 配列を読み取り専用にする
    ReadOnlyCollection<int> arr = Array.AsReadOnly(source);

    // 元になった配列に変更を加える
    source[0] = 5;

    // 読み取り専用コレクションの要素を列挙
    for (int i = 0; i < arr.Count; i++) {
      Console.Write("{0}, ", arr[i]);
    }
    Console.WriteLine();
  }
}
実行結果
5, 1, 2, 3, 4, 

ReadOnlyCollectionについては汎用ジェネリックコレクション(1) Collection/ReadOnlyCollection §.ReadOnlyCollectionでも解説しています。

§6 要素の並べ替え

§6.1 リバース (Reverse)

Array.Reverseメソッドを使うことで配列内の全要素の並びを逆順に(リバース)することが出来ます。 逆順にする最初のインデックスと要素数を指定することで、配列内の一部分だけを逆順にすることも出来ます。

配列内の要素をリバースする
using System;

class Sample {
  static void Main()
  {
    int[] arr = {0, 1, 2, 3, 4};

    // 配列内の全要素の並びを逆順にする
    Array.Reverse(arr);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    // 配列内のインデックス2から3個分の要素の並びを逆順にする
    Array.Reverse(arr, 2, 3);

    foreach (int elem in arr) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
4, 3, 2, 1, 0, 
4, 3, 0, 1, 2, 

ジャグ配列をリバースすることも出来ますが、その場合は1段目のみが逆順になります。 2段目以降をリバースする場合は、格納されている配列に対して個別にArray.Reverseメソッドを呼び出してリサイズする必要があります。

なお、Array.Reverseメソッドでは多次元配列を逆順にすることは出来ません。 Array.Reverseメソッドに多次元配列を指定すると、RankExceptionがスローされます。

§6.2 ソート (Sort)

Array.Sortメソッドを使うことで配列内の全要素をソートして昇順に並べ替えることが出来ます。 ソートする最初のインデックスと要素数を指定することで、配列内の一部分だけをソートすることも出来ます。

配列内の要素をソートする
using System;

class Sample {
  static void Main()
  {
    int[] arr1 = {4, 2, 0, 3, 1};

    // 配列内の全要素を昇順に並べ替える
    Array.Sort(arr1);

    foreach (int elem in arr1) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();

    int[] arr2 = {4, 3, 2, 1, 0};

    // 配列内のインデックス2から3個分の要素を昇順に並べ替える
    Array.Sort(arr2, 2, 3);

    foreach (int elem in arr2) {
      Console.Write("{0}, ", elem);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 4, 
4, 3, 0, 1, 2, 

Array.Sortメソッドを使ったソートについてより詳しくは基本型のソートと昇順・降順でのソートで解説しています。 また、Array.Sortメソッドを使って多次元配列やジャグ配列をソートする方法についてはジャグ配列・多次元配列のソートで詳しく解説しています。

§6.3 シャッフル

Arrayクラスには配列をランダムな順に並べ替える、つまりシャッフルを行うメソッドは用意されていません。 配列をシャッフルするには乱数を使ってソートするなどの方法で独自に実装する必要があります。

配列をシャッフルする具体的な実装例は配列・コレクションのシャッフルで紹介しています。

§7 要素の検索

ここでは配列内にある要素の検索を行うメソッドについて紹介します。 なお、配列の型によっては引数でIComparerなどのインターフェイスが必要になりますが、ここではそういった事項に付いては触れません。 必要に応じて大小関係の定義と比較等価性の定義と比較を参照してください。

§7.1 要素の位置の検索 (IndexOf, LastIndexOf, BinarySearch)

Array.IndexOfメソッドを使うことで配列内における指定した値を持つ要素の位置を検索して取得することが出来ます。 このメソッドは配列の前方から検索を行うため、配列内に同一の値を持つ要素がある場合は、そのうちより小さい(最初に見つかった)インデックスを返します。 逆に、Array.LastIndexOfメソッドは配列の後方から検索を行うため、より大きい(前方から見て最後に見つかった)インデックスを返します。 どちらのメソッドも、配列内に該当する要素が無い場合は-1が返されます。

なお、Array.IndexOf, Array.LastIndexOfメソッドでは多次元配列内の要素の位置を検索することは出来ません。 メソッドに多次元配列を指定すると、RankExceptionがスローされます。

要素の位置の検索
using System;

class Sample {
  static void Main()
  {
    int[] arr = {0, 1, 0, 2, 0, 3};

    // 値に0が格納されている最初のインデックスを取得
    Console.WriteLine("IndexOf(0) = {0}", Array.IndexOf(arr, 0));

    // 値に0が格納されている最後のインデックスを取得
    Console.WriteLine("LastIndexOf(0) = {0}", Array.LastIndexOf(arr, 0));

    // 値に4が格納されている最初のインデックスを取得
    Console.WriteLine("IndexOf(4) = {0}", Array.IndexOf(arr, 4));
  }
}
実行結果
IndexOf(0) = 0
LastIndexOf(0) = 4
IndexOf(4) = -1

Array.BinarySearchメソッドを使うことでも配列内における要素の位置を検索出来ます。 Array.IndexOfメソッドは先頭から順に1つずつ要素を検証していくのに対し、Array.BinarySearchメソッドは二分探索によって要素を検索するため、より高速に要素の位置を検索できます(要素数nの配列ではIndexOfがO(n)であるのに対し、BinarySearchはO(log2n)となる)。 ただし、いくつかの点でArray.IndexOfメソッドとは異なるため、単純にArray.IndexOfメソッドの代わりとしてArray.BinarySearchメソッドを適用できるとは限りません。

まず、Array.BinarySearchメソッドを使って要素の位置を検索する場合、配列の要素が昇順に並んでいる必要があります。 そのため、Array.BinarySearchメソッドを使う場合は、あらかじめ昇順に並んでいる配列に対して使うか、呼び出す前にあらかじめ配列をソートしておくようにします。 昇順に並んでいない配列に対してArray.BinarySearchメソッドを使っても例外がスローされることはありませんが、正しい結果は得られません。

次に、Array.BinarySearchメソッドは該当する要素が見つかった場合はそのインデックスを返しますが、Array.IndexOfメソッドとは異なり、該当する要素が複数ある場合でもそのうちもっとも小さいインデックスが返されるとは限りません。 また、配列内に該当する要素が無い場合は負の数が返されます(-1が返されるとは限りません)。

二分探索による要素の位置の検索
using System;

class Sample {
  static void Main()
  {
    // 要素が昇順に並んでいる配列
    int[] arr = {0, 1, 1, 2, 2, 2, 3, 4, 4, 4, 4, 5};

    // IndexOfメソッドで値に2が格納されているインデックスを取得
    Console.WriteLine("IndexOf(2) = {0}", Array.IndexOf(arr, 2));

    // BinarySearchメソッドで値に2が格納されているインデックスを取得
    Console.WriteLine("BinarySearch(2) = {0}", Array.BinarySearch(arr, 2));

    // 値に7が格納されているインデックスを取得
    Console.WriteLine("IndexOf(7) = {0}", Array.IndexOf(arr, 7));
    Console.WriteLine("BinarySearch(7) = {0}", Array.BinarySearch(arr, 7));
  }
}
実行結果
IndexOf(2) = 3
BinarySearch(2) = 5
IndexOf(7) = -1
BinarySearch(7) = -13

なお、Array.BinarySearchメソッドもArray.IndexOfメソッドと同様、多次元配列内の要素の位置を検索することは出来ません。 メソッドに多次元配列を指定すると、RankExceptionがスローされます。

§7.1.1 IndexOfとBinarySearchの速度比較

参考までに、Array.IndexOfメソッドとArray.BinarySearchメソッドを使った場合の速度の違いを比較した結果を紹介します。 次のように、IndexOfメソッドを使った検索では配列の要素数が増えるにつれ多大な時間がかかるようになっていますが、BinarySearchメソッドを使った検索では配列の要素数が増えても比較的短い時間で結果が得られています。

要素数が100の場合
Length = 100
IndexOf:      00:00:00.0001335
BinarySearch: 00:00:00.0003081
IndexOf:      00:00:00.0000226
BinarySearch: 00:00:00.0000201
IndexOf:      00:00:00.0000257
BinarySearch: 00:00:00.0000195
要素数が10000の場合
Length = 10000
IndexOf:      00:00:00.1274312
BinarySearch: 00:00:00.0033034
IndexOf:      00:00:00.1212156
BinarySearch: 00:00:00.0018985
IndexOf:      00:00:00.1183027
BinarySearch: 00:00:00.0019239
要素数が100000の場合
Length = 100000
IndexOf:      00:00:12.8952917
BinarySearch: 00:00:00.0278454
IndexOf:      00:00:12.9067753
BinarySearch: 00:00:00.0270422
IndexOf:      00:00:13.0714185
BinarySearch: 00:00:00.0276836
検証に使ったコード
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // ソート済みの要素を含む配列を作成
    var arr = new int[100 * 1000];

    for (var i = 0; i < arr.Length; i++) {
      arr[i] = i;
    }

    Console.WriteLine("Length = {0}", arr.Length);

    // 3回試行
    for (var c = 0; c < 3; c++) {
      var tick = Environment.TickCount;

      // IndexOfメソッドでの探索
      var rand1 = new Random(tick);
      var sw1 = Stopwatch.StartNew();

      for (var n = 0; n < arr.Length; n++) {
        Array.IndexOf(arr, rand1.Next(0, arr.Length));
      }

      sw1.Stop();

      Console.WriteLine("IndexOf:      {0}", sw1.Elapsed);

      // BinarySearchメソッドでの探索
      var rand2 = new Random(tick);
      var sw2 = Stopwatch.StartNew();

      for (var n = 0; n < arr.Length; n++) {
        Array.BinarySearch(arr, rand2.Next(0, arr.Length));
      }

      sw2.Stop();

      Console.WriteLine("BinarySearch: {0}", sw2.Elapsed);
    }
  }
}

§7.2 述語を使った検索 (Find, Exists, etc.)

Arrayクラスには、述語(Predicate)を使った要素の検索や取得を行うメソッドがいくつか用意されています。 条件を記述してデリゲートの形式でこれらのメソッドに渡すことで、その条件に見合う要素を検索・取得することが出来ます。 これらのメソッドを使うことで、for文・foreach文で列挙してif文で各要素を条件と照らし合わせて検索するといった処理を、メソッドを呼び出すだけで済ませる事が出来るようになります。

Arrayクラスには、述語を使った要素の検索・取得を行うことが出来るメソッドとして次のようなものが用意されています。

述語を使った要素の検索・取得を行うメソッド
メソッド 機能
Find 述語で定義された条件と一致する最初の要素を取得する
FindLast 述語で定義された条件と一致する最後の要素を取得する
FindAll 述語で定義された条件と一致するすべての要素を取得する
FindIndex 述語で定義された条件と一致する最初の要素のインデックスを取得する
FindLastIndex 述語で定義された条件と一致する最後の要素のインデックスを取得する
Exists 述語で定義された条件と一致する要素があるかどうかを判断する
TrueForAll 述語で定義された条件とすべての要素が合致するかどうかを判断する

次の例では、値が偶数かどうかを返すメソッドを作成し、それを述語として使用することで配列内にある偶数の要素を検索・取得しています。

述語を使った検索
using System;

class Sample {
  // 引数で与えられた数が偶数かどうか
  static bool IsEven(int val)
  {
    if (val % 2 == 0)
      return true;
    else
      return false;
  }

  static void Main()
  {
    int[] arr = {1, 4, 3, 5, 2, 6};

    // 「値が偶数かどうか」という述語となるデリゲート
    Predicate<int> isEven = IsEven;

    // 値が偶数の要素とそのインデックスを取得
    Console.WriteLine("Find: {0}", Array.Find(arr, isEven));
    Console.WriteLine("FindIndex: {0}", Array.FindIndex(arr, isEven));
    Console.WriteLine("FindLast: {0}", Array.FindLast(arr, isEven));
    Console.WriteLine("FindLastIndex: {0}", Array.FindLastIndex(arr, isEven));

    // 値が偶数の要素をすべて取得
    Console.Write("FindAll: ");

    foreach (int elem in Array.FindAll(arr, isEven)) {
      Console.Write("{0}, ", elem);
    }

    Console.WriteLine();

    // 値が偶数の要素が存在するかどうか
    Console.WriteLine("Exists? {0}", Array.Exists(arr, isEven));

    // すべての要素の値が偶数かどうか
    Console.WriteLine("TrueForAll? {0}", Array.TrueForAll(arr, isEven));
  }
}
実行結果
Find: 4
FindIndex: 1
FindLast: 6
FindLastIndex: 5
FindAll: 4, 2, 6, 
Exists? True
TrueForAll? False

§8 全要素の変換 (ConvertAll)

Array.ConvertAllメソッドを使うことで、配列内の全要素の値を変換することが出来ます。 このメソッドでは、変換処理を記述したメソッドをConverterデリゲートの形式で指定することで、その処理を配列内の全要素に適用し、その結果を格納した配列を取得することが出来ます。

このメソッドを使うことにより、ある配列を別の型の配列に変換したり、全要素に同じ関数を適用して値を変換する事が出来ます。

文字列型配列を数値型配列に変換する
using System;

class Sample {
  // 文字列を数値に変換するメソッド
  static int ToInt(string str)
  {
    return int.Parse(str); // 数値として不正な文字列等の処理については省略
  }

  static void Main()
  {
    string[] strings = {"0", "1", "2", "3"};

    // 文字列型配列内のすべての要素を数値に変換する
    int[] ints = Array.ConvertAll(strings, ToInt);

    foreach (int i in ints) {
      Console.Write("{0}, ", i);
    }
    Console.WriteLine();
  }
}
実行結果
0, 1, 2, 3, 

配列内の全ての値を変換する
using System;

class Sample {
  // 度数を弧度に変える
  static double ToRadian(double degree)
  {
    return degree * Math.PI / 180.0;
  }

  static void Main()
  {
    double[] degrees = {0.0, 90.0, 180.0, 270.0};

    // 配列内の値をすべて弧度に変換する
    double[] radians = Array.ConvertAll(degrees, ToRadian);

    foreach (double rad in radians) {
      Console.Write("{0}, ", rad);
    }
    Console.WriteLine();

    // 配列を文字列の配列に変換して連結する
    Console.WriteLine(string.Join(", ", Array.ConvertAll(degrees, Convert.ToString)));
  }
}
実行結果
0, 1.5707963267949, 3.14159265358979, 4.71238898038469, 
0, 90, 180, 270

§9 要素の列挙 (ForEach)

Array.ForEachメソッドを使うと、配列の要素を列挙してすべての要素に対して同一の処理を施すことが出来ます。 このメソッドでは、処理を記述したメソッドをActionデリゲートの形式で指定することで、配列内の個々の要素を引数としてそのメソッドが呼び出されます。 foreach文の内側でメソッド呼び出しを行う代わりに、このメソッドを使ってより簡単に記述することが出来ます。

Array.ForEachメソッドで配列内の全要素を列挙する
using System;

class Sample {
  // 引数の値をゼロ埋めして表示
  static void Print(int val)
  {
    Console.WriteLine("{0:D3}", val);
  }

  static void Main()
  {
    int[] arr = {0, 1, 2, 3, 4};

    // 配列内の値をゼロ埋めして表示
    Array.ForEach(arr, Print);

    Console.WriteLine();

    // 上記の処理をforeach文で記述した場合
    foreach (int elem in arr) {
      Print(elem);
    }
  }
}
実行結果
000
001
002
003
004

000
001
002
003
004

このメソッドは配列内の各要素を列挙するだけであるため、元の配列に変更を加えるような処理を行うことは出来ません。 そういった処理を行いたい場合はfor文やArray.ConvertAllメソッドを使います。