暗黙的なインデックスアクセス(Implicit index access)
C# 13では、暗黙的に最終要素からの位置を指定するインデックス演算子(^)がオブジェクト初期化式中で利用できるようになりました。
C#では、配列の要素にアクセスする際に、最終要素からの相対位置で指定できる仕組みがあります。添え字(インデックス)にインデックス演算子(^)を付加して指定すると、先頭要素からではなく最終要素からの位置の指定になります。位置の指定は最終要素から「何番目」となるので、以下のリストのように最終要素は「^1」となります。
int[] iArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Console.WriteLine("{0}", iArray[^1]); // 9
C# 10から使えるようになったオブジェクト初期化子においては、プロパティが配列であるとき、以下のリストのようにインデックス演算子で初期値の設定が可能です。
class Dice
{
public int[] roll { get; set; } = new int[6];
}
var dice1 = new Dice()
{
roll =
{
[0] = 1,
[1] = 2,
[2] = 3,
[3] = 4,
[4] = 5,
[5] = 6,
}
};
Console.WriteLine("{0}", dice1.roll[4]); // 5
しかしながら、インデックス演算子(^)を使おうとすると、オブジェクト初期化子中以外では問題がないのに、C# 13以前ではコンパイルエラーとなっていました。これがC# 13では以下のリストのように問題なく使えるようになりました。
+++スクリプト+++
var dice2 = new Dice()
{
roll =
{
[^1] = 1,
[^2] = 2,
[^3] = 3,
[^4] = 4,
[^5] = 5,
[^6] = 6,
}
};
Console.WriteLine("{0}", dice2.roll[4]); // 2
イテレータと非同期メソッドで使えるようになったrefとunsafe(ref and unsafe in iterators and async methods)
C# 13では、refとunsafeがイテレータと非同期メソッドで利用できるようになりました。
従来は、イテレータメソッド(yield returnを含むメソッド)と非同期メソッドの中にref変数、ref構造体、unsafeブロックを含めることはできませんでした。C# 13では、これらのもたらす効率的なメモリ管理がイテレータや非同期メソッド内で利用できるようになり、大量のデータ処理が必要な場合や、レイテンシーを抑えたい用途での効率向上が期待できます。
以下のリストは、2値を返すだけシンプルなものですが、イテレータメソッド内でref変数とref構造体を使う例です。
foreach(int d in Enumerate())
{
Console.WriteLine("{0}", d); // 123、1
}
IEnumerable<int> Enumerate()
{
int a = 123;
ref int r = ref a; // ref変数
yield return r;
Span<int> data = [1]; // ref構造体
yield return data[0];
}
続けて以下のリストは、非同期メソッド内でunsafeブロックを使う例です。ref変数とref構造体のコードは上記とほとんど同じなので省略します。配布サンプルを参照してください。
await GetAsync(); // 123、3、456
async Task GetAsync()
{
…ref変数とref構造体の例は省略…
int i = 123;
unsafe // unsafeブロック
{
int *p = &i;
*p = 456;
}
Console.WriteLine("{0}", i);
await Task.Yield();
}
最後の以下のリストは、非同期なイテレータメソッドの例です。
await foreach(int d in AsyncEnumerate())
{
Console.WriteLine("{0}", d); // 456、2、456
}
async IAsyncEnumerable<int> AsyncEnumerate()
{
int x = 123;
ref int r = ref x; // ref変数
r = 456;
await Task.Yield();
yield return x;
Span<int> d = [1, 2, 3]; // ref構造体
int v = d[1];
await Task.Yield();
yield return v;
int i = 123;
unsafe // unsafeブロック
{
int *p = &i;
*p = 456;
}
await Task.Yield();
yield return i;
}
最後のリストで、ref構造体の1要素を別変数に入れて返していますが、これはref構造体の要素をそのまま返せないためです。このように、ref変数とref構造体は、awaitやyieldをまたいで使用できない制約があります。
[NOTE]unsafeブロック
C#では、いわゆるポインタ型を使ったコードは安全でないとして既定で無効となっています。しかしながら、より実行効率を優先したコードや、メモリのきめ細かな操作のためにポインタ型や固定サイズのバッファーを有効にできるunsafeブロックが用意されています。コンパイラに/unsafeオプションを指定するか、プロジェクト設定ファイル(.csproj)の<PropertyGroup>に<AllowUnsafeBlocks>true</AllowUnsafeBlocks>を追加することで、unsafeブロックが利用できるようなります。
