System.Collections.ArrayList は、.NET Framework 1.0 から提供されている 可変長の非ジェネリック配列 です。List<T> がジェネリックの登場(.NET 2.0)以降に主役を譲ったため新規コードで使うことは推奨されませんが、レガシーコードや一部の COM 連携、DataTable 関連の API ではいまだに目にします。本記事では ArrayList の 内部構造・インターフェース・性能特性・使いどころ を整理します。
ArrayList とは
- 名前空間:
System.Collections - 内部構造:
object[]バッファ + 現在の要素数Count+ 確保済み容量Capacity - 容量が足りなくなると 倍々(× 2)に拡張(最初は 4 から始まる)
- 要素の型はすべて
object。値型は ボックス化 されて格納される
using System.Collections;
var list = new ArrayList();
list.Add(1); // int → object にボックス化
list.Add("hello"); // 参照型はそのまま
list.Add(3.14); // double → object にボックス化
int n = (int)list[0]; // 取り出すときはキャストが必須
すべて object 経由で扱うため、型安全でなく、値型ではボックス化のオーバーヘッドが発生 します。これが List<T> に取って代わられた最大の理由です。
サポートするインターフェース
ArrayList は次のインターフェースを実装しています。
| インターフェース | 役割 |
|---|---|
IList |
添字アクセス・挿入・削除を持つ順序付きコレクション |
ICollection |
Count・CopyTo・同期サポートなどコレクション共通機能 |
IEnumerable |
foreach で列挙可能 |
ICloneable |
Clone() による浅いコピーをサポート |
IList
IList は インデックスでアクセスできるコレクション を表すインターフェースです。ArrayList の中核となる機能はすべてこのインターフェース由来です。
public interface IList : ICollection
{
object? this[int index] { get; set; }
int Add(object? value);
void Insert(int index, object? value);
void Remove(object? value);
void RemoveAt(int index);
bool Contains(object? value);
int IndexOf(object? value);
void Clear();
bool IsFixedSize { get; }
bool IsReadOnly { get; }
}
ICollection
ICollection は要素数の取得とコピー、スレッド同期サポートを規定する基底インターフェースです。
public interface ICollection : IEnumerable
{
int Count { get; }
object SyncRoot { get; }
bool IsSynchronized { get; }
void CopyTo(Array array, int index);
}
SyncRoot を使った排他制御は古い設計で、現在は lock (専用オブジェクト) か並行コレクション(System.Collections.Concurrent)の利用が推奨されます。
IEnumerable / ICloneable
IEnumerable の詳細は IEnumerable と IEnumerator の記事 を、ICloneable の浅いコピーと深いコピーの違いは ICloneable インターフェースの記事 を参照してください。
ArrayList.Clone() は 浅いコピー を返します。要素が参照型の場合、参照だけがコピーされる点に注意してください。
var src = new ArrayList { new[] { 1, 2, 3 } };
var dst = (ArrayList)src.Clone();
((int[])dst[0])[0] = 99;
Console.WriteLine(((int[])src[0])[0]); // 99(同じ配列を共有)
主な API と計算量
| 操作 | API | 平均計算量 | 備考 |
|---|---|---|---|
| 末尾追加 | Add(obj) |
O(1) 償却 | 容量超過時は O(n) で再確保 |
| 末尾削除 | RemoveAt(Count - 1) |
O(1) | |
| 添字アクセス | list[i] |
O(1) | 配列と同じ |
| 任意位置挿入 | Insert(i, obj) |
O(n) | 後続要素を 1 つずらす |
| 任意位置削除 | RemoveAt(i) / Remove(obj) |
O(n) | |
| 検索 | Contains / IndexOf |
O(n) | 線形探索 |
| ソート | Sort() |
O(n log n) | 内部は Array.Sort |
| 二分探索 | BinarySearch(obj) |
O(log n) | 事前に整列が必要 |
Capacity を事前に設定すると再確保を防げます。要素数の見当が付くなら必ず指定しましょう。
var list = new ArrayList(capacity: 10_000);
for (int i = 0; i < 10_000; i++) list.Add(i);
list.TrimToSize(); // Capacity を Count まで切り詰める
ボックス化のコスト
ArrayList の最大の落とし穴は 値型のボックス化 です。
var arr = new ArrayList();
for (int i = 0; i < 1_000_000; i++) arr.Add(i); // 100 万回ボックス化
long sum = 0;
foreach (var x in arr) sum += (int)x; // 100 万回アンボックス化
ヒープ割り当てと GC 圧、キャスト命令のコストが積み重なり、List<int> と比べて 数倍~10 倍以上遅くなる ことも珍しくありません。値型を扱うなら無条件に List<T> を選ぶべきです。
List<T> との比較
| 観点 | ArrayList |
List<T> |
|---|---|---|
| 型安全 | × object のキャストが必要 |
◯ コンパイル時に保証 |
| 値型のボックス化 | 発生する | 発生しない |
| パフォーマンス | 劣る | 良い |
| LINQ 対応 | Cast<T>() 経由で必要 |
そのまま使える |
| 推奨度 | 新規コードでは非推奨 | 標準 |
新規コードでは 常に List<T> を使うのが鉄則です。
使いどころ
ジェネリックの登場以降、ArrayList を積極的に選ぶ理由はほぼありません。実務で出会うのは次のような場面です。
- レガシーコードのメンテナンス:.NET Framework 1.x 時代から続くコードベース。
- COM 相互運用や古い API:戻り値が
ArrayListやIListのままになっている API。 - 動的な異種要素の集合:型が混在し
objectで扱うのが妥当な場面。とはいえ現在はList<object>で十分です。 - 学習目的:コレクションの内部実装(容量倍々拡張など)を理解する材料として。
ArrayList から List<T> への移行
// Before
ArrayList legacy = GetLegacyList(); // 中身は string が入っているとする
foreach (object o in legacy)
{
string s = (string)o;
Console.WriteLine(s.ToUpper());
}
// After(LINQ の Cast<T> で型付き列挙に変換)
foreach (string s in legacy.Cast<string>())
{
Console.WriteLine(s.ToUpper());
}
// さらに安全に:実体ごと List<string> へ移す
List<string> modern = legacy.Cast<string>().ToList();
API 境界が IList で型が固定的に決まっている場合、Cast<T>() で挟むだけでも型安全な世界に橋渡しできます。
まとめ
ArrayListは 非ジェネリックな可変長配列。内部はobject[]。IList/ICollection/IEnumerable/ICloneableを実装。- 添字アクセスは O(1)、末尾追加は O(1) 償却、任意位置の挿入・削除は O(n)。
- 値型を入れると ボックス化が起きる。新規コードでは
List<T>一択。 - レガシー API との接続点でだけ顔を出す存在。出会ったら
Cast<T>()で素早く型安全な世界に持ち込もう。
次回以降、BitArray / Hashtable / Queue / SortedList / Stack と、System.Collections の他のクラスを順に扱います。