C#における配列の初期化は、単なるプログラミングの基本操作に留まらず、アプリケーションのパフォーマンスや安全性に直結する重要な要素です。
特に数値型配列を「0」で満たす処理は、画像処理、データ通信のバッファ管理、数値計算アルゴリズムのリセット処理など、多岐にわたるシーンで頻繁に登場します。
C#が動作する .NET ランタイムは、安全性の観点からメモリの割り当て時に自動的にゼロ初期化を行う仕組みを持っていますが、開発者が意図的に「既存の配列を0に戻す」場合や、「パフォーマンスを極限まで追求する」場合には、適切な手法を選択する必要があります。
本記事では、単純な new 演算子から、最新の Span<T> を活用した高速な手法、さらにはアンマネージメモリの操作まで、C#で配列を0で初期化・リセットするための全手法を徹底解説します。
C#における配列初期化の基本原理
C#で配列を扱う際、まず理解しておくべきは「既定値」の概念です。
C#の数値型(int, float, double など)や参照型、構造体にはそれぞれ既定値が決まっており、数値型の場合は一律で 0 が割り当てられます。
new 演算子による自動初期化
C#で最も一般的な配列の作成方法は new 演算子を使用することです。
このとき、.NET の共通言語ランタイム (CLR) はメモリを確保すると同時に、その領域をゼロで塗りつぶす(Zero-init)ことを保証しています。
using System;
public class Program
{
public static void Main()
{
// 要素数5のint型配列を宣言
// この時点で全ての要素は自動的に0で初期化されている
int[] numbers = new int[5];
Console.WriteLine("--- new演算子による初期化結果 ---");
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($"Index {i}: {numbers[i]}");
}
}
}
--- new演算子による初期化結果 ---
Index 0: 0
Index 1: 0
Index 2: 0
Index 3: 0
Index 4: 0
この挙動により、開発者が明示的にループを回して 0 を代入する必要はありません。
ただし、これは「新しく配列を生成する場合」に限られます。
既存の配列を再利用(再初期化)したい場合は、別の手法が必要になります。
既存の配列を0でリセットする主要手法
一度作成した配列の値をすべて 0 に戻したい場合、いくつかの選択肢があります。
用途と .NET のバージョンに応じて最適なものを選択しましょう。
Array.Clear メソッド(伝統的な手法)
.NET Framework 時代から存在する最も標準的な方法が Array.Clear です。
指定した範囲の要素をその型の既定値(数値なら 0)に設定します。
Array.Clear の特徴
- 広範な互換性:古い .NET バージョンでも動作します。
- 部分的なクリア:配列全体だけでなく、特定のインデックスから特定の長さ分だけを 0 にすることが可能です。
- 型安全性:要素の型に応じた適切な既定値をセットします。
using System;
public class Program
{
public static void Main()
{
int[] data = { 10, 20, 30, 40, 50 };
// 配列のインデックス0からすべての要素をクリア(0にする)
Array.Clear(data, 0, data.Length);
Console.WriteLine("Array.Clear 実行後:");
Console.WriteLine(string.Join(", ", data));
}
}
Array.Clear 実行後:
0, 0, 0, 0, 0
Array.Fill メソッド(.NET Core 2.0以降)
.NET Core 2.0 以降では、配列を任意の値で埋めるための Array.Fill が導入されました。
0 で埋めることも容易です。
// 全要素を0で埋める
Array.Fill(data, 0);
Array.Clear との違いは、「既定値以外(例えば 1 など)でも埋められる」点にあります。
0 でリセットすることだけが目的であれば Array.Clear の方が意図が明確になりますが、可読性の観点から Array.Fill を好む開発者もいます。
Span<T>.Clear メソッド(高パフォーマンス・モダン C#)
.NET Core 2.1 および C# 7.2 で導入された Span<T> は、現代の C# において最も推奨される高速な初期化手法の一つです。
Span<T>.Clear() は、内部的に非常に最適化されたコード(ランタイムによっては SIMD 命令など)を使用してメモリ領域をゼロクリアします。
using System;
public class Program
{
public static void Main()
{
int[] data = { 1, 2, 3, 4, 5 };
// 配列をSpanとして扱い、高速にクリア
Span<int> span = data.AsSpan();
span.Clear();
Console.WriteLine("Span.Clear 実行後:");
foreach (var item in data)
{
Console.Write($"{item} ");
}
}
}
Span.Clear 実行後:
0 0 0 0 0
Span<T> を使用するメリットは、配列だけでなく、スタック領域(stackalloc)やネイティブメモリに対しても共通のインターフェースで高速なゼロクリアを行える点にあります。
多次元配列とジャグ配列の初期化
C# には「多次元配列(int[,])」と「ジャグ配列(int[][])」の 2 種類が存在し、初期化の挙動が異なります。
多次元配列(矩形配列)の場合
多次元配列も new した時点ですべて 0 になります。
リセットする場合は Array.Clear がそのまま利用可能です。
int[,] matrix = new int[3, 3]; // すべて0
Array.Clear(matrix, 0, matrix.Length); // 全要素を0にリセット
ジャグ配列(配列の配列)の場合
ジャグ配列は「配列を格納した配列」であるため、外側の配列を new しただけでは内側の配列自体が null になってしまいます。
内側の各要素を 0 で初期化するには、各行ごとに new する必要があります。
int[][] jagged = new int[3][];
for (int i = 0; i < jagged.Length; i++)
{
jagged[i] = new int[5]; // ここで各行が0で初期化される
}
パフォーマンス比較と内部挙動
大量のデータを扱う場合、どの手法が最も速いのかは重要な関心事です。
なぜ Span.Clear や Array.Clear は速いのか
通常の for ループで 0 を代入する場合、インデックスの境界チェックが毎回発生します。
一方で、Array.Clear や Span.Clear は、ランタイム内部で memset と呼ばれる低レベルなメモリ操作命令や、CPU の SIMD (Single Instruction, Multiple Data) 命令セットを利用して、複数の要素を一度に 0 で塗りつぶします。
パフォーマンスの傾向(一般的な指標)
- Span<T>.Clear() : 最速。最新の最適化が適用される。
- Array.Clear() : 非常に高速。大規模な配列で威力を発揮。
- Array.Fill(data, 0) : 高速だが、0 以外の値を埋める汎用性のためのオーバーヘッドが僅かにある場合がある。
- for ループ : 最も遅い。コンパイラの最適化がかからない限り、境界チェックのコストが蓄積する。
実験的なベンチマークの視点
数千、数万要素を超える配列をリセットする場合、for ループと Span.Clear では数倍から十数倍の速度差が出ることがあります。
リアルタイム性が求められるゲーム開発や、高スループットなサーバーサイド処理では、Span<T> の活用が不可欠です。
高度なシナリオ:スタック割り当てとアンマネージドメモリ
特殊なケースとして、ヒープメモリ(通常の配列)以外を 0 で初期化する方法についても触れておきます。
stackalloc によるスタック配列
短寿命な小さな配列を作成する場合、stackalloc を使用してスタック領域にメモリを確保することがあります。
// stackallocはデフォルトでゼロ初期化される(C# 8.0以降)
Span<int> buffer = stackalloc int[128];
// もし意図的にリセットしたいなら
buffer.Clear();
注意点:stackalloc の初期化をスキップする [SkipLocalsInit] 属性を使用している場合は、手動で Clear() を呼ぶまで中身は不定(ゴミデータが入っている)となります。
アンマネージメモリ(NativeMemory)
.NET 6 以降では、NativeMemory.AllocZeroed を使用して、確保と同時に 0 埋めされたネイティブメモリを取得できます。
using System.Runtime.InteropServices;
// 100要素分のメモリを確保し、0で埋める
unsafe
{
int* ptr = (int*)NativeMemory.AllocZeroed(100, sizeof(int));
// 処理...
NativeMemory.Free(ptr);
}
手法の選び方(決定版ガイド)
状況に応じて最適な手法を選ぶための指針を以下にまとめます。
1. 新しく配列を作る場合
迷わず new T[size] を使用してください。
ランタイムが最も効率的な方法で 0 初期化済みのメモリを提供します。
2. 既存の配列をすべて 0 に戻す場合
- .NET Core 2.1 以降 / .NET 5, 6, 7, 8+:
span.Clear()が最適です。 - .NET Framework または古い環境:
Array.Clear()を使用してください。
3. 配列の一部だけを 0 にしたい場合
範囲指定が容易な Array.Clear(array, index, length) または、スライスを利用した array.AsSpan(index, length).Clear() が適しています。
4. 0 以外の値(1 や -1 など)で埋めたい場合
Array.Fill() または span.Fill() を使用します。
この際、Clear() よりもわずかにコストがかかる可能性があることを念頭に置いてください。
まとめ
C#で配列を 0 で初期化・リセットする手法は、言語の進化とともに洗練されてきました。
| 手法 | 適したシーン | パフォーマンス | 備考 |
|---|---|---|---|
new T[] | 配列の新規作成 | 高(ランタイム管理) | 自動的に 0 で初期化される |
Array.Clear | 既存配列のリセット | 高 | 互換性が高く、部分的クリアに便利 |
Array.Fill | 特定の値(0含む)で埋める | 中〜高 | 汎用性が高い |
Span.Clear | 現代の標準リセット手法 | 最高 | .NET Core 2.1 以降の推奨 |
for ループ | 特殊な条件が必要な場合 | 低 | 通常は使用を避けるべき |
基本的には、新規作成時は new、既存配列のリセット時は Span.Clear を選択するのが、現代の C# 開発におけるベストプラクティスです。
特にパフォーマンスが要求されるアプリケーションでは、Span<T> のようなメモリ効率の高い API を使いこなすことで、ガベージコレクションの負荷を抑えつつ、高速なデータ処理を実現できます。
本記事で紹介した各手法の内部的な振る舞いや特徴を理解し、開発しているアプリケーションのターゲット環境や要件に合わせて最適なメソッドを選択してください。






