C# 8.0(.NET Core 3.0)で System.Index 型と ^(インデックス演算子)が追加されました。これによりコレクションの末尾から数えたインデックスを、array.Length - n のような計算なしに書けるようになりました。
従来の末尾要素へのアクセス
末尾から n 番目の要素を取得するには、従来は配列の長さを使って計算する必要がありました。
int[] numbers = { 10, 20, 30, 40, 50 };
int last = numbers[numbers.Length - 1]; // 50
int second = numbers[numbers.Length - 2]; // 40
^ 演算子(インデックス演算子)
C# 8.0 からは ^n と書くことで「末尾から n 番目」を表せます。
int[] numbers = { 10, 20, 30, 40, 50 };
int last = numbers[^1]; // 50(末尾から 1 番目)
int second = numbers[^2]; // 40(末尾から 2 番目)
int third = numbers[^3]; // 30(末尾から 3 番目)
^0 は末尾の「1 つ後ろ」(= 要素数と同じ位置)になるため、要素へのアクセスに使うと範囲外例外になります。^1 が最後の要素です。
System.Index とは
^ 演算子はコンパイル時に System.Index 型に変換されます。System.Index は位置(Value)と方向(IsFromEnd)の 2 つの情報を持つ読み取り専用の構造体です。
Index fromStart = new Index(2); // 先頭から 2 番目(Index.Value = 2, IsFromEnd = false)
Index fromEnd = new Index(1, fromEnd: true); // 末尾から 1 番目(Index.Value = 1, IsFromEnd = true)
// ^ 演算子は以下と同義
Index i = ^3; // new Index(3, fromEnd: true)
Console.WriteLine(i.IsFromEnd); // True
Console.WriteLine(i.Value); // 3
静的ファクトリメソッドでも作成できます。
Index a = Index.FromStart(2); // 先頭から 2 番目
Index b = Index.FromEnd(1); // 末尾から 1 番目(^1 と同じ)
Index.Start と Index.End
Index 型には定義済みの定数が 2 つあります。
Index start = Index.Start; // 先頭(0 と同じ)
Index end = Index.End; // 末尾の 1 つ後ろ(^0 と同じ)
これらは主に Range と組み合わせて使われます(Range については別記事で紹介します)。
GetOffset メソッド
Index の実際のオフセット(配列の何番目か)を求めるには GetOffset(length) を使います。
Index i = ^2;
int offset = i.GetOffset(5); // コレクション長 5 の場合 → 3(5 - 2 = 3)
Console.WriteLine(offset); // 3
カスタム型に対応させる
独自のコレクション型で ^ 演算子を使えるようにするには、int を受け取るインデクサーと int 型の Length または Count プロパティを定義するだけで十分です。コンパイラが GetOffset を自動的に適用してくれます。
class MyList
{
private int[] _data = { 1, 2, 3, 4, 5 };
public int Length => _data.Length;
public int this[int index] => _data[index];
}
var list = new MyList();
int last = list[^1]; // 5
まとめ
| 書き方 | 意味 |
|---|---|
array[0] |
先頭の要素 |
array[^1] |
末尾の要素 |
array[^n] |
末尾から n 番目の要素 |
Index.FromEnd(n) |
末尾から n 番目の Index を明示的に作成 |
i.GetOffset(length) |
Index を実際のオフセットに変換 |
System.Index と ^ 演算子を使うことで、array.Length - 1 のような冗長な計算が不要になります。配列・文字列・Span<T> などで広く使えます。