paramsコレクション(Params collections)
C# 13では、params(可変長引数)に、コレクション式で使える型を利用できるようになりました。
C#のメソッドには、可変長引数を受け取る仕組みがあります。これは、paramsと呼ばれます。配列を受け取るメソッドにおいてparamsを使うと、個別の変数をそのままメソッドの引数として渡すことができます。
double a = 3.14159, b = 1.41421, c = 2.2362, d = 2.71828, e = 1.61803; ave = CalcAverageParams(a, b, c, d, e); // 直接渡せる Console.WriteLine($"With params: {ave}"); // With params: 2.225662 double CalcAverageParams(params double[] a) // paramsを指定して配列で受ける { double sum = 0; for (int i = 0; i < a.Length; i++) { sum += a[i]; } return sum / a.Length; }
従来は、paramsで渡せるのは基本的に配列のみでしたが、C# 13では配列を含むコレクション式を渡せるようになりました。コレクション式とは、C# 12で導入された、配列やコレクションを大カッコ([ ])で初期化できる記法です。このコレクション式については第4回で紹介しました。
C# 13では、上記のリストは以下のように書き換えられます。
double ave; double a = 3.14159, b = 1.41421, c = 2.2362, d = 2.71828, e = 1.61803; ave = CalcAverageList(a, b, c, d, e); Console.WriteLine($"With List: {ave}"); // With List: 2.225662 double CalcAverageList(params List<double> a) …関数の中身はCountプロパティに書き換えただけなので省略… ave = CalcAverageIEnumerable(a, b, c, d, e); Console.WriteLine($"With IEnumerable: {ave}"); // With IEnumerable: 2.225662 double CalcAverageIEnumerable(params IEnumerable<double> a) …関数の中身はforeach文に書き換えただけなので省略… ave = CalcAverageReadOnlySpan(a, b, c, d, e); Console.WriteLine($"With ReadOnlySpan: {ave}"); // With ReadOnlySpan: 2.225662 double CalcAverageReadOnlySpan(params ReadOnlySpan<double> a) …関数の中身は配列版に同じなので省略… ave = CalcAverageSpan(a, b, c, d, e); Console.WriteLine($"With Span: {ave}"); // With Span: 2.225662 double CalcAverageSpan(params Span<double> a) …関数の中身は配列版に同じなので省略…
新しいロックセマンティクス(New lock object)
C# 13では、lock文でLockオブジェクトを特別扱いするようになりました。
lock文は、マルチスレッド環境において排他制御を行うための構文です。以下のように、排他制御に使うオブジェクト(同期オブジェクト)を、排他制御の必要な期間(クリティカルセクション)の開始時に自動でロックして(排他ロック)、終了時に自動で解放(アンロック)する処理を記述できます。
同期オブジェクトがロックされている間、他のスレッドは同期オブジェクトをロックしたくてもできないので、クリティカルセクション内で排他的な処理を記述できます。
同期オブジェクト生成 lock(同期オブジェクト) // 排他ロック(二重ロック不可) { クリティカルセクション // ここを実行できるのは常に単一のスレッドのみ } // アンロック
従来のC#では、同期オブジェクトには任意のオブジェクト(objectオブジェクトなど)などを使うことで、lock文において排他制御のためのSystem.Threading.Monitorクラスを使うコードに展開されていました。Monitorクラスによる排他制御は速度面で不利な面があるため、.NET 9で新しい排他制御のためのクラスLockが追加されました。これに伴い、同期オブジェクトにはLockオブジェクトを使います。lock文の書き方自体は変わりませんが、C# 13ではSystem.Threading.Lockクラスを使うコードに展開されます。
以下は、2つのロック方法にかかる時間を比較する例です。複数のスレッドを起動し、ダミーの計算処理を実行しています。スレッドの起動と終了の時間をStopwatchクラスで計測して合計値とともに表示します。
using System.Diagnostics; const int NumberOfThread = 10; // スレッドの個数 const int NumberOfLoop = 100000; // スレッド内で実行するループの回数 long sum; // ダミーの計算処理用 var sw = new Stopwatch(); // 実行時間計測用 // 従来のlock var syncObject = new object(); // 任意のオブジェクト sum = 0; // 計算値をリセット sw.Reset(); // 計測開始 sw.Start(); Parallel.For(0, NumberOfThread, i => // スレッド起動 { for (int j = 0; j < NumberOfLoop; j++) // 繰り返し開始 { lock (syncObject) // 同期オブジェクトのロック { int tmp = sum + i; // ダミーの計算処理 sum = tmp + j; } } }); sw.Stop(); // 計測終了 Console.Write("sync object: {0}: {1}\n", sum, sw.Elapsed); // 結果表示 // 新しいlock var lockObject = new Lock(); // Lockオブジェクトとする sum = 0; // 計算値をリセット sw.Reset(); sw.Start(); Parallel.For(0, NumberOfThread, i => { for (int j = 0; j < NumberOfLoop; j++) { lock (lockObject) // 同期オブジェクト(Lockオブジェクト)のロック { int tmp = sum + i; // ダミーの計算処理 sum = tmp + j; } } }); sw.Stop(); Console.Write("lock object: {0}: {1}\n", sum, sw.Elapsed);
実行すると、Lockオブジェクトを用いた繰り返しの方が高速であることが分かります。
sync object: 500400000: 00:00:00.0213048 lock object: 500400000: 00:00:00.0045708