目次

Ver. 12.0

※ 2024/7/6 現在、プレビュー版です。

リリース時期 2024/11
同世代技術
  • .NET 9.0

執筆予定: C# 13.0 トラッキング issue

params コレクション

コレクション式で使える型であれば何でも params にできるようになりました。

static void M1(params List<int> x) { }
static void M2(params IEnumerable<int> x) { }
static void M3(params Span<int> x) { }
static void M4(params ReadOnlySpan<int> x) { }

M1(1, 2);
M2(1, 2);
M3(1, 2);
M4(1, 2);

需要が高いのは ReadOnlySpan で、 params T[]params ReadOnlySpan<T> に変更すればそれだけでパフォーマンスの改善が見込めます。

実際、 .NET 9 では、string.JoinTask.WhenAll などのメソッドに params ReadOnlySpan<T> なオーバーロードが増えています。

// .NET 8 以前なら Join(string, string[])
// .NET 9 以降なら Join(string, ReadOnlySpan<string>)
var joiend = string.Join(",", "a", "b", "c");

このため、自分で params を使わない場合でも、 「.NET 9 にアップグレードして再コンパイルするだけでアプリのパフォーマンスがちょっと改善する」という間接的なメリットがあります。

詳しくは「params コレクション」で説明しています。

部分プロパティ

プロパティとインデクサーも partial にできるようになりました。

例えば、C# 13 と同世代の .NET 9 では、GeneratedRegex をプロパティにできるようになりました。

using System.Text.RegularExpressions;

partial class MyPatterns
{
    [GeneratedRegex(@"\d{4}")]
    public static partial Regex FourDigits { get; } // プロパティになった。
}

詳しくは「部分プロパティ」で説明します。

ref 構造体のインターフェイス実装

先に「アンチ制約」の説明だけ追加: 「アンチ制約

Lock クラスに対する lock

.NET 9 で Lock クラス(System.Threading 名前空間)という新しい lock 用の型が追加されたことに伴って、 lock ステートメントでこの Lock クラスを特別扱いするようになりました。 既存の lock (Monitor.Enter に展開される)と異なり、以下のようなコードに展開されます。

var syncObject = new Lock();

// lock (syncObject)
using (syncObject.EnterScope())
{
}

詳しくは「Lock クラス」で説明しています。

ref/unsafe をイテレーター/非同期メソッド中に書けるように

ref ローカル変数ref 構造体の変数、 unsafe ブロックを、 イテレーター非同期メソッド内で使えるようになりました。

イテレーターと非同期メソッドは内部の仕組み的に非常に似ているにも関わらず、 この2者で微妙に制限のかかり方が違ったんですが、 それも C# 13 でそろいました。

以下のコードで、行末コメントで ⭕ を付けている部分が C# 13 で新たにコンパイルできるようになったコードです。

IEnumerable<object?> Enumerate()
{
    unsafe { } // ⭕

    yield return null;

    Span<byte> data = [];

    yield return null;

    int x = 123;
    ref int r = ref x; // ⭕
}

async Task GetAsync()
{
    unsafe { }

    await Task.Yield();

    Span<byte> data = []; // ⭕

    await Task.Yield();

    int x = 123;
    ref int r = ref x; // ⭕
}

async IAsyncEnumerable<object?> EnumerateAsync()
{
    unsafe { } // ⭕

    await Task.Yield(); yield return null;

    Span<byte> data = []; // ⭕

    await Task.Yield(); yield return null;

    int x = 123;
    ref int r = ref x; // ⭕
}

元々、原理的にはこう書いても問題ないことはわかっていたんですが、 正しく判定するのにコストがかかる割に、需要は低いだろうということでエラーにしていました。 C# 13 で書けるようになったのは、前述のLock クラスに対する lock のついでだそうです。 (Lock クラスの EnterScope が ref 構造体を使っています。)

ただし、これは yieldawait をまたがない場合に限って許されます。 例えば以下のコードは C# 13 でもコンパイル エラーを起こします。

IEnumerable<object?> Enumerate()
{
    int x = 123;
    ref int r = ref x;
    yield return null;
    r = 456;
}

async Task GetAsync()
{
    int x = 123;
    ref int r = ref x;
    await Task.Yield();
    r = 456;
}

\e (エスケープ文字のエスケープ シーケンス)

文字・文字列リテラル中のエスケープ シーケンス\e (U+001B、エスケープ文字)が追加されました。

例えば、コンソール アプリで以下のように書くことで、文字列の色を変えたり装飾したりできます。

Console.WriteLine("\e[31mred text");
Console.WriteLine("\e[4munderlined text");
Console.WriteLine("\e[0mreset style");

\e エスケープ シーケンス

機能追加の背景などについてはブログ記事「\e (エスケープ文字のエスケープ シーケンス)」で説明しています。

その他

その他、ほぼバグ修正レベルの機能がいくつかあります。

オブジェクト初期化子中の ^ 演算子

以下のように、オブジェクト初期化子中の [] の中でインデックスの ^ 演算子を使えるようになりました。

// これが C# 12 以前はコンパイル エラーを起こしてた。
var c = new C { [^1] = 1 };

// これなら昔からコンパイルできる。
// (オブジェクト初期化子はこれと同じコードに展開されるはずなのに。)
c[^1] = 1;

class C
{
    // インデクサーと Length さえ持っていれば c[^i] と書けるようになる。
    // c[c.Length - i] 扱い。
    public int Length => 1;
    public int this[int i] { get => i; set { } }
}

デリゲートの自然な型の改善

デリゲートの自然な型の決定の際、 メソッド グループに対する型決定がちょっと賢くなったそうです。 同名のインスタンス メソッドと拡張メソッドがあるとき、インスタンス メソッドを優先的に見るようになりました。

例えば以下のようなクラスがあったとします。

public class C
{
    public void M() { } // インスタンス メソッド M と、
}

public static class E
{
    public static void M(this C c, object o) { } // 同名の拡張メソッド。
}

この C 型のインスタンス x に対して x.M と書いたとき、 C# 12 までは自然な型を決定できなかったのに対して、 C# 13 ではインスタンスメソッドを優先的に見ます。

var x = new C();

// オーバーロード解決ではインスタンスメソッド優先。
x.M();      // C.M()
x.M(""); // E.M(C, object)

// 型の明示があると昔から大丈夫だった。
Action a = x.M;         // C.M()
Action<object> b = x.M; // E.M(C, object)

// var を使う。
// これが C# 13 から行けるように。
// インスタンス メソッド優先で、Action 型になる。
var z = x.M;

更新履歴

ブログ