Arrayクラス

CLRにおける配列の基本クラスです。

int[] array1 = new int[5];
Array array2 = Array.CreateInstance(typeof(int), 5);

Type type1 = array1.GetType(); // System.Int32[]
Type type2 = array2.GetType(); // System.Int32[]


array1[3] = 10;
array2.SetValue(10, 3);

int r1 = array1[3];
int r2 = (int)array2.GetValue(3);

実装しているインターフェイス

[SerializableAttribute]
[ComVisibleAttribute(true)]
public abstract class Array :
    ICloneable,
    IList,
    ICollection,
    IEnumerable,
    IStructuralComparable,
    IStructuralEquatable
構文 - Array クラス (System) | MSDN

プロパティ

プロパティ 内容
bool IsFixedSize Arrayが固定サイズかどうか
bool IsReadOnly Arrayが読み取り専用かどうか
bool IsSynchronized Arrayへのアクセスが同期されている (スレッドセーフである) かどうか。つねにfalse
int Length Arrayのすべての次元内の要素の総数
long LongLength Arrayのすべての次元内の要素の総数 (64ビット整数)
int Rank Arrayのランク (次元数) を取得する。たとえば1次元配列は1で、2次元は2
object SyncRoot Arrayへのアクセスを同期するために使用できるオブジェクト
プロパティ - Array クラス (System) | Microsoft Learn

メソッド

主要なメソッド
  名前 説明
作成 CreateInstance(Type, Int32) Typeと長さを指定して、0から始まるインデックスを持つ1次元の配列を作成する
CreateInstance(Type, Int32, Int32) Typeと次元の長さを指定して、0から始まるインデックスを持つ2次元の配列を作成する
CreateInstance(Type, Int32, Int32, Int32) Typeと次元の長さを指定して、0から始まるインデックスを持つ3次元の配列を作成する
CreateInstance(Type, Int32[]) Typeと次元の長さを指定して、0から始まるインデックスを持つ多次元の配列を作成する
Empty<T>() 空の配列を返す
初期化 Initialize() 値型の既定のコンストラクターを呼び出し、この値型の各要素を初期化する
Clear(Array, Int32, Int32) 配列内にある要素の範囲を、各要素の型の既定値に設定する
複製 Clone() Arrayの簡易コピーを作成する
CopyTo(Array, Int32) 1次元配列のすべての要素を、指定の1次元配列にコピーする
Copy(Array, Array, Int32) 他の配列に、指定範囲の要素をコピーする
ConstrainedCopy(Array, Int32, Array, Int32, Int32) 他の配列に、指定範囲の要素をコピーする。コピーが完全に成功しないならば、変更が適用されないことが保証される
AsReadOnly<T>(T[]) 指定の配列をラップする、読み取り専用のラッパーを作成する
要素数 GetLength(Int32) 指定の次元にある要素の数を表す、32ビット整数を取得する
GetLongLength(Int32) 指定の次元にある要素の数を表す、64ビット整数を取得する
値の取得 GetValue(Int32) 1次元の配列内の、指定位置にある値を取得する
GetValue(Int32, Int32) 2次元の配列内の、指定位置にある値を取得する
GetValue(Int32, Int32, Int32) 3次元の配列内の、指定位置にある値を取得する
GetValue(Int32[]) 多次元の配列内の、指定位置にある値を取得する
値の設定 SetValue(Object, Int32) 1次元の配列内の指定位置にある要素に、値を設定する
SetValue(Object, Int32, Int32) 2次元の配列内の指定位置にある要素に、値を設定する
SetValue(Object, Int32, Int32, Int32) 3次元の配列内の指定位置にある要素に、値を設定する
SetValue(Object, Int32[]) 多次元の配列内の指定位置にある要素に、値を設定する
検索 FindAll<T>(T[], Predicate<T>) 指定条件に一致する、すべての要素を取得する
Find<T>(T[], Predicate<T>) 指定条件に一致する要素を検索し、最もインデックスの小さい要素を返す
FindLast<T>(T[], Predicate<T>) 指定条件に一致する要素を検索し、最もインデックスの大きい要素を返す
FindIndex<T>(T[], Predicate<T>) 指定条件に一致する要素を検索し、最もインデックスの小さい要素のインデックスを返す
FindLastIndex<T>(T[], Predicate<T>) 指定条件に一致する要素を検索し、最もインデックスの大きい要素のインデックスを返す
IndexOf(Array, Object) 指定のオブジェクトを検索し、1次元の配列でそのオブジェクトが最初に見つかった位置のインデックスを返す
LastIndexOf(Array, Object) 指定のオブジェクトを検索し、1次元の配列でそのオブジェクトが最後に見つかった位置のインデックスを返す
BinarySearch(Array, Object) 1次元の並べ替え済み配列全体の中から、特定の要素を検索する
Exists<T>(T[], Predicate<T>) 指定の配列に、指定条件に一致する要素が含まれているかどうかを判断する
TrueForAll<T>(T[], Predicate<T>) 配列内のすべての要素が、指定条件に一致するかどうかを調べる
並べ替え Sort(Array) Arrayの各要素のIComparable実装を使用して、1次元配列全体の要素を並べ替える
Sort(Array, IComparer) 1次元の配列内の要素を、指定のIComparerを使用して並べ替える
Sort(Array, Array, IComparer) 2つの1次元配列オブジェクト (一方のオブジェクトがキーを格納し、他方のオブジェクトがそれらに対応する項目を格納する) を、最初の配列内のキーに基づき、指定のIComparerを使用して並べ替える
Reverse(Array) 1次元の配列内の、要素のシーケンスを反転させる
変換 ConvertAll<TInput, TOutput>(TInput[], Converter<TInput, TOutput>) ある型の配列を、別の型の配列に変換する
Resize<T>(T[], Int32) 1次元の配列の要素数を、指定した新しいサイズに変更する
その他 ForEach<T>(T[], Action<T>) 指定の配列内の各要素に対して、指定された処理を実行する
GetEnumerator() IEnumeratorを得られる
GetLowerBound(Int32) 配列内の指定の次元の、最初の要素のインデックスを取得する
GetUpperBound(Int32) 配列内の指定の次元の、最後の要素のインデックスを取得する
メソッド - Array クラス (System) | Microsoft Learn

初期化

Clear()

型の既定値を設定できます。

int[] a = new int[] { 1, 2, 3, 4, 5 };
Array.Clear(a, 1, 3); // 1, 0, 0, 0, 5

要素の範囲を省略したいならば、IListにキャストした上でArray.IList.Clear()を呼びます。Array.IList.Clear メソッド (System) | Microsoft Learn

.NET 6以降ならば、Array.Clear()で同様に処理できます。

int[] b = new int[] { 1, 2, 3, 4, 5 };
((IList)b).Clear(); // 0, 0, 0, 0, 0

配列の複製

配列を複製できるメソッドは3つあり、これらは複製できる範囲と適用可能な次元に違いがあります。

メソッド 複製範囲 適用可能な次元
Clone() すべての要素 任意の次元
CopyTo() 指定位置から末尾までの要素 1次元のみ
Copy() 指定位置から末尾までの要素、または指定位置から指定数の要素 任意の次元

これらのメソッドではすべてシャローコピー (Shallow copy) で処理されるため、参照が複製されます。c# - Deep Copy with Array - Stack Overflow

public object Clone()
Array.Clone メソッド (System) | MSDN
int[] src = new int[] { 1, 2, 3 };
object dest = src.Clone(); // 1, 2, 3
public void CopyTo(
    Array array, // コピー先の配列
    int index    // コピーを開始するインデックス
)
Array.CopyTo メソッド (Array, Int32) (System) | MSDN Array.CopyTo メソッド (System) | MSDN

コピー先の要素が不足するとArgumentExceptionが投げられ、充足ならば元の値が残ります。

int[] src = new int[] { 1, 2, 3 };

int[] dest1 = new int[] { 9, 9, 9, 9 };
src.CopyTo(dest1, 1); // 9, 1, 2, 3

int[] dest2 = new int[] { 9, 9, 9, 9, 9 };
src.CopyTo(dest2, 1); // 9, 1, 2, 3, 9

int[] dest3 = new int[3];
src.CopyTo(dest3, 1); // ArgumentException「ターゲット配列の長さが足りません。destIndex、長さ、および配列の最小値を確認してください。」
public static void Copy(
    Array sourceArray,      // コピー元の配列
    Array destinationArray, // コピー先の配列
    int length              // コピーする要素の数
)
Copy(Array, Array, Int32) - Array.Copy メソッド (System) | Microsoft Learn

配列の途中からコピーしたいならば、開始インデックスを指定できる形式を用います。

public static void Copy(
    Array sourceArray,      // コピー元の配列
    int sourceIndex,        // コピーを開始するインデックス
    Array destinationArray, // コピー先の配列
    int destinationIndex,   // コピー先の配列に格納を開始するインデックス
    int length
)
Copy(Array, Int32, Array, Int32, Int32) - Array.Copy メソッド (System) | Microsoft Learn
int[] src = new int[] { 1, 2, 3 };

int[] dest1 = new int[] { 9, 9 };
Array.Copy(src, 1, dest1, 0, 2); // 2, 3

int[] dest2 = new int[] { 9, 9, 9 };
Array.Copy(src, 1, dest2, 0, 2); // 2, 3, 9

int[] dest3 = new int[1];
Array.Copy(src, 1, dest3, 0, 2); // ArgumentException「ターゲット配列の長さが足りません。destIndex、長さ、および配列の最小値を確認してください。」

多次元配列をコピーするときには、すべての要素が1次元に並んでいると見なしてsourceIndexなどのインデックスを指定します。

サンプルコード

次のクラスが要素である配列を、複製する場合を考えます。

class MyClass
{
    public int data;
    public MyClass(int data) { this.data = data; }
}
Clone()
MyClass[] src = { new MyClass(1), new MyClass(2) };

MyClass[] dest = (MyClass[])src.Clone();

src[0].data = 3;
Console.Write(dest[0].data); // 3
CopyTo()
MyClass[] src = { new MyClass(1), new MyClass(2) };

MyClass[] dest = new MyClass[src.Length];
src.CopyTo(dest, 0);

src[0].data = 3;
Console.Write(dest[0].data); // 3
Copy()
MyClass[] src = { new MyClass(1), new MyClass(2) };

MyClass[] dest = new MyClass[src.Length];
Array.Copy(src, dest, src.Length);

src[0].data = 3;
Console.Write(dest[0].data); // 3

多次元配列の複製

Clone()とCopy()で、1次元配列と同様に複製できます。一方でCopyTo()は多次元に対応しないため、例外が発生します。

int[,] src = { { 1, 2, 3 }, { 4, 5, 6 } };

// Clone()
int[,] dest1 = (int[,])src.Clone();

// CopyTo()
int[,] dest2 = new int[src.GetLength(0), src.GetLength(1)];
src.CopyTo(dest2, 0); // System.ArgumentException 要求されたアクションに対しては、1 次元配列のみがサポートされます。

// Copy()
int[,] dest3 = new int[src.GetLength(0), src.GetLength(1)];
Array.Copy(src, dest3, src.Length);
ジャグ配列

ジャグ配列の要素は配列のため、シャローコピーにより参照がコピーされることで、コピー先の配列への変更がコピー元へ影響します。

int[][] src = new int[][]
{
    new int[] { 1, 2, 3 },
    new int[] { 4, 5, 6 }
};

int[][] dest = (int[][])src.Clone();

dest[1][1] = 10;          // コピー先の配列を変更
Console.Write(src[1][1]); // 10 (コピー元の配列へ影響する)
// 多次元配列 の場合
int[,] src = { { 1, 2, 3 }, { 4, 5, 6 } };

int[,] dest = (int[,])src.Clone();

dest[1, 1] = 10;          // コピー先の配列を変更
Console.Write(src[1, 1]); // 5 (コピー元の配列は影響されない)
2次元配列から1次元配列への複製

Copy()では「System.RankException 指定された配列の次元数は同じでなければなりません。」として異なる次元へ複製できないため、Buffer.BlockCopy()でバイト単位で複製します。c# - Does Array.Copy work with multidimensional arrays? - Stack Overflow

int[,] src = { { 1, 2, 3 }, { 4, 5, 6 } }; // コピー元の2次元配列

int[] dest = new int[src.Length]; // コピー先の1次元配列
int count = src.GetLength(1) * sizeof(int);

for (int i = 0; i < src.Rank; i++)
{
    int offset = count * i;
    Buffer.BlockCopy(src, offset, dest, offset, count);
}

一方でジャグ配列はその要素が配列のため、Copy()で処理できます。

int[][] src = new int[3][]; // コピー元の2次元配列
src[0] = new int[] { 1, 2, 3, 4 };
src[1] = new int[] { 5, 6 };
src[2] = new int[] { 7, 8, 9 };

int[] dest = new int[9]; // コピー先の1次元配列
Array.Copy(src[0], 0, dest, 0, 4);
Array.Copy(src[1], 0, dest, 4, 2);
Array.Copy(src[2], 0, dest, 6, 3);

汎用的には次のようにします。

int sum = 0;
for (int i = 0; i < src.Length; i++)
{
    sum += src[i].Length;
}
int[] dest = new int[sum];

for (int i = 0, offset = 0; i < src.Length; i++)
{
    int length = src[i].Length;
    Array.Copy(src[i], 0, dest, offset, length);

    offset += length;
}

要素の検索

IndexOf()

指定オブジェクトに一致する要素を検索できます。

public static int IndexOf(
    Array array, // 検索対象の1次元配列
    object value // 検索するオブジェクト
)
Array.IndexOf メソッド (Array, Object) (System) | MSDN

見つかった場合は一致した最初の要素のインデックス、さもなくば配列のインデックスの下限から-1したインデックスが返されます。

int[] a = new int[] { 1, 2, 3, 4, 5 };

int r1 = Array.IndexOf(a, 3);  // 2
int r2 = Array.IndexOf(a, 10); // -1 (インデックスの下限0から-1した値)

比較はObject.Equals()メソッドで行われます。 arrayがT[]の配列ならばIndexOf<T>が用いられ、T.Equals()で行われます。

これをそのオブジェクトのEquals()で行うことを確実にしたいならば、IndexOf<T>を用います。この場合、比較はT.Equals()で行われます。

public static int IndexOf<T>(
    T[] array,
    T value
)
Array.IndexOf(T) メソッド (T[], T) (System) | MSDN
int[] a = new int[] { 1, 2, 3 };
int r = Array.IndexOf<int>(a, 3); // 2

これをより柔軟な条件で比較したいならば、FindIndex<T>(T[], Predicate<T>)を用います。なおList<T>には同様のメソッドとして、List<T>.IndexOf(T)があります。

Find()

指定条件に一致する要素を検索できます。

public static T Find<T>(
    T[] array,
    Predicate<T> match
)
Array.Find(T) メソッド (T[], Predicate(T)) (System) | MSDN

見つかった場合は一致した最初の要素、さもなくば型Tの既定値が返されます。

void MyClass()
{
    int[] a = new int[] { 1, 2, 3, 4, 5 };

    int r1 = Array.Find(a, Predicate); // 4
}

bool Predicate(int x)
{
    return 3 < x;
}

ラムダ式を用いると、以下のようにも記述できます。

int r2 = Array.Find(a, (x) => { return 3 < x; }); // 4
int r3 = Array.Find(a,  x  =>          3 < x   ); // 4

int r4 = Array.Find(a,  x  =>         10 < x   ); // 0 (0はintの既定値)

IndexOf()とは異なり、返されるのはインデックスではなく要素です。

string[] str = new string[] { "a", "b", "c" };
string result = Array.Find(str, (x) => { return x == "b"; }); // "b"

検索対象が配列ではないならば、Enumerable.FirstOrDefault()で同様に検索できます。

配列の検索

配列と配列を比較し、その要素の並びが一致する位置を得るには文字列探索 (string searching) の手法を用います。

  • Knuth-Morris-Pratt法
  • Boyer-Moore法

Exists()

指定の配列に、指定条件に一致する要素が含まれているかどうかを確認できます。

public static bool Exists<T> (
    T[] array,
    Predicate<T> match
    );
Array.Exists<T>(T[], Predicate<T>) メソッド (System) | Microsoft Learn
int[] a = new int[] { 1, 2, 3 };
bool r = Array.Exists(a, x => x == 2); // true

内部的にはArray.FindIndex()を呼び出し、その戻り値が-1ではないことを確認するのと同じです。Exists - array.cs

TrueForAll()

配列内のすべての要素が、指定条件に一致するかどうかを確認できます。

bool r1 = Array.TrueForAll(new int[] { 1, 2, 3 }, x => x <= 2); // false
bool r2 = Array.TrueForAll(new int[] { 1, 2, 2 }, x => x <= 2); // true

配列が空ならば、条件にかかわらずtrueが返されます。Returns - Array.TrueForAll<T>(T[], Predicate<T>) Method (System) | Microsoft Learn

bool r3 = Array.TrueForAll(new int[] { }, x => x == 1); // true

要素の並べ替え

Sort()

並べ替えの方法やその範囲によって、次のようにオーバーロードされています。

  • Sort(Array)
  • Sort(Array, Array)
  • Sort(Array, Array, IComparer)
  • Sort(Array, Array, Int32, Int32)
  • Sort(Array, Array, Int32, Int32, IComparer)
  • Sort(Array, IComparer)
  • Sort(Array, Int32, Int32)
  • Sort(Array, Int32, Int32, IComparer)
  • Sort<T>(T[])
  • Sort<T>(T[], Comparison<T>)
  • Sort<T>(T[], IComparer<T>)
  • Sort<T>(T[], Int32, Int32)
  • Sort<T>(T[], Int32, Int32, IComparer<T>)
  • Sort<TKey, TValue>(TKey[], TValue[])
  • Sort<TKey, TValue>(TKey[], TValue[], IComparer<TKey>)
  • Sort<TKey, TValue>(TKey[], TValue[], Int32, Int32)
  • Sort<TKey, TValue>(TKey[], TValue[], Int32, Int32, IComparer<TKey>)
オーバーロード - Array.Sort メソッド (System) | Microsoft Learn
int[] a1 = { 3, 4, 6, 2, 4, 1 };
Array.Sort(a1);
// 1, 2, 3, 4, 4, 6

int[] a2 = { 1, 2, 3, 4, 5 };
Array.Sort(a2, (x, y) => { return y - x; });
// 5, 4, 3, 2, 1

int[] a3 = { 1, 2, 3, 4, 5 };
Array.Sort(a3, (x, y) => { return (x == 3) ? -1 : 0; });
// 3, 1, 2, 4, 5

パーティションのサイズによって、使用されるアルゴリズムが異なります。

パーティションのサイズ アルゴリズム
16以下 挿入ソート (insertion sort)
2 * log Nを超える (Nは配列の範囲) ヒープソート (heapsort)
その他 クイックソート (quicksort)
Remarks - Array.Sort Method (System) | Microsoft Learn
// 要素数16
int[] a4 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
Array.Sort(a4, (x, y) => { return 0; });
// 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16

Array.Sort(a4, (x, y) => { return -1; });
// 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1

a4 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
Array.Sort(a4, (x, y) => { return (x == 5) ? -1 : 0; });
// 5, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
// 要素数17
int[] a5 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
Array.Sort(a5, (x, y) => { return 0; });
// 1, 15, 14, 13, 12, 11, 10, 16, 9, 7, 6, 5, 4, 3, 2, 8, 17

a5 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
Array.Sort(a5, (x, y) => { return -1; }); // System.ArgumentExceptionが発生
// HResult : -2147024809 (0x80070057)
// IComparer.Compare() メソッドから矛盾する結果が返されたため、並べ替えできません。値をそれ自体と比較したときに等しい結果にならないか、またはある値を別の値と繰り返し比較したときに異なる結果が生じます。

a5 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
Array.Sort(a5, (x, y) => { return (x == 5) ? -1 : 0; });
// 5, 1, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 4, 3, 2, 16, 17
// 要素数18
int[] a6 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 };
Array.Sort(a6, (x, y) => { return 0; });
// 1, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 17, 18
自然順 (natural order)

Win32のStrCmpLogicalW()メソッドを用いることで実現できます。StrCmpLogicalW function (shlwapi.h) - Win32 apps | Microsoft Learn

public class NaturalStringComparer : IComparer<string>
{
    [SuppressUnmanagedCodeSecurity]
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
    private static extern int StrCmpLogicalW(string psz1, string psz2);

    public int Compare(string x, string y)
    {
        return StrCmpLogicalW(x, y);
    }
}
sorting - Natural Sort Order in C# - Stack Overflow

これを用いると、次のように並べ替えられます。

string[] s = { "B10", "A2", "A11", "A1", "B2" };
Array.Sort(s);
// "A1", "A11", "A2", "B10", "B2"

Array.Sort(s, new NaturalStringComparer());
// "A1", "A2", "A11", "B2", "B10"

Reverse()

要素の順番を、反転させられます。

public static void Reverse(
    Array array,
    int index,
    int length
)
Array.Reverse メソッド (Array, Int32, Int32) (System) | MSDN
int[] array = new int[] { 1, 2, 3, 4, 5, 6 };
Array.Reverse(array, 1, 4); // { 1, 5, 4, 3, 2, 6 }

要素の変換

ConvertAll<TInput, TOutput>()

public static TOutput[] ConvertAll<TInput, TOutput>(
    TInput[] array,
    Converter<TInput, TOutput> converter
)
Array.ConvertAll(TInput, TOutput) Method (TInput[], Converter(TInput, TOutput)) (System) | MSDN
public delegate TOutput Converter<in TInput, out TOutput>(
    TInput input
)
Converter(TInput, TOutput) Delegate (System) | MSDN
static void Main()
{
    double[] src = new double[] { 1.5, 2.5, 3.5 };

    // 方法1:
    Converter<double, int> convert = new Converter<double, int>(DoubleToInt);
    int[] dst1 = Array.ConvertAll<double, int>(src, convert);

    // 方法2:一時変数をインライン化し、変換メソッドをdelegateで記述
    int[] dst2 = Array.ConvertAll(src, new Converter<double, int>(delegate (double x) { return (int)x; }));

    // 方法3:delegateをラムダ式で記述
    int[] dst3 = Array.ConvertAll(src, new Converter<double, int>((x) => { return (int)x; }));

    // 方法4:Convertクラスのメソッドで記述
    int[] dst4 = Array.ConvertAll(src, Convert.ToInt32);
}

static int DoubleToInt(double x)
{
    return (int)x;
}
c# - What is the actual use of Array.ConvertAll compared to e.g. looping through the array via foreach? - Stack Overflow

たとえばbyte配列からshort配列へは、次のようにも変換できます。bytearray - Convert byte array to short array in C# - Stack Overflow

byte[] src = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
short[] dst = new short[src.Length / 2];

Buffer.BlockCopy(src, 0, dst, 0, src.Length);

Console.Write("{0:x4}", dst[0]); // 0201
Console.Write("{0:x4}", dst[1]); // 0403
Console.Write("{0:x4}", dst[2]); // 0605

byte配列から数値への変換

ForEach<T>(T[], Action<T>)

配列内の各要素に対して、指定の処理を実行できます。

public static void ForEach<T>(
    T[] array,
    Action<T> action
)
Array.ForEach<T>(T[], Action<T>) メソッド (System) | Microsoft Learn

ListのForEach()とは異なり、これは静的メソッドです。

int[] a = new int[] { 1, 2, 3 };

//
Array.ForEach(a, delegate (int x)
{
    Console.Write(x);
});

// ラムダ式によるに記述
Array.ForEach(a, x => Console.Write(x));

// Action<T>型のメソッドに各要素を渡すだけならば、そのメソッドを指定するだけで良い
Array.ForEach(a, Console.Write);

メソッドだけを指定するとき、そのメソッドにConditional属性が付けられていると、ビルドによってはメソッドが存在しない可能性があるためにCS1618でエラーとなります。

Microsoft Learnから検索