What's new in C# 14

3/6に.NET公式Xで、次期LTS版となる.NET 10 Preview1のリリースが発表され、なんか盛り上がってるぽい。
気になったので、C# 14の新機能を調べてみました。
間違ってたらご指摘いただけますと幸いです。

# 目次
* [`field`キーワード](https://qiita.com/yr46/items/19f83949f963271b7609#field%E3%82%AD%E3%83%BC%E3%83%AF%E3%83%BC%E3%83%89)
* [暗黙的なスパン変換](https://qiita.com/yr46/items/19f83949f963271b7609#%E6%9A%97%E9%BB%99%E7%9A%84%E3%81%AA%E3%82%B9%E3%83%91%E3%83%B3%E5%A4%89%E6%8F%9B)
* [`nameof`演算子での未バインドのジェネリック型のサポート](https://qiita.com/yr46/items/19f83949f963271b7609#nameof%E6%BC%94%E7%AE%97%E5%AD%90%E3%81%A7%E3%81%AE%E6%9C%AA%E3%83%90%E3%82%A4%E3%83%B3%E3%83%89%E3%81%AE%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E5%9E%8B%E3%81%AE%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88)
* [修飾子付きラムダ式パラメーターの単純化](https://qiita.com/yr46/items/19f83949f963271b7609#%E4%BF%AE%E9%A3%BE%E5%AD%90%E4%BB%98%E3%81%8D%E3%83%A9%E3%83%A0%E3%83%80%E5%BC%8F%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%AE%E5%8D%98%E7%B4%94%E5%8C%96)

# `field`キーワード
`field`キーワードは、プロパティのアクセサ内でコンパイラが生成するバックフィールドにアクセスするためのコンテキストキーワードです。​これにより、明示的にバックフィールドを宣言することなく、プロパティの`get`および`set`アクセサ内で直接そのフィールドを操作できます。

従来、プロパティに対してカスタムのロジックを追加する際には、明示的にバックフィールドを宣言し、`get`および`set`アクセサを実装する必要がありました。​例えば、`null`値の設定を防ぐためには、以下のように記述していました。
```c#
private string _message;
public string Message
{
    get => _message;
    set => _message = value ?? throw new ArgumentNullException(nameof(value));
}
```
C# 14では、`field`キーワードを使用することで、プロパティの自動実装と同様に簡潔に記述しつつ、カスタムロジックを組み込むことが可能となりました。​上記の例は以下のように簡略化できます。
```c#
public string Message
{
    get;
    set => field = value ?? throw new ArgumentNullException(nameof(value));
}
```
このように、`set`アクセサ内で`field`キーワードを使用して、`null`値の設定を防ぐロジックを直接記述できます。 

### 何がいいの？
1. **バックフィールドを明示せずにプロパティのロジックを記述できる**
上の例でいうと、`_message`を記述する必要がなくなったことにより可読性が向上。自動プロパティとの統一性も保たれる。
1. **バックフィールドの名前衝突を防げる**
従来の方法では、開発者が`_messag`や`_value`などのバックフィールド名を自分で管理する必要があった。
しかし、C# 14の`field`は、コンパイラが自動生成するバックフィールドを参照するため、フィールド名の衝突を防ぐことができる。
1. **保守性の向上**
ルールを直接プロパティに書けるので、保守しやすい。

**要するに、`field`は「シンプルなコードでプロパティにロジックを追加したい」場合に特に有用!**


:::note warn
`field`キーワードの導入により、既存のコードで`field`という名前のフィールドが存在する場合、名前の衝突が発生する可能性があります。​そのような場合、`@field`または`this.field`として参照することで曖昧さを解消できます。
:::

補足：C# 13、,.NET 9でもプレビュー機能として使えるとのことです。


# 暗黙的なスパン変換
C# 14では、`System.Span<T>`および`System.ReadOnlySpan<T>`に対する**暗黙的な型変換**が強化され、これらの型をより自然に操作できるようになりました。​これにより、パフォーマンス向上と安全性の確保が期待できます。
```c#
Span<int> numbers = new int[] { 1, 2, 3 };
ReadOnlySpan<int> readOnlyNumbers = numbers;
```
このように、`T[]`や`List<T>`から`Span<T>`への変換や、`Span<T>`から`ReadOnlySpan<T>`への変換が容易になりました。

### 何がいいの？
1. **メモリ割り当てを減らし、パフォーマンスを向上**
`Span<T>`はヒープアロケーションを行わない構造体(`struct`)であり、配列や`List<T>`の一部を安全にスライス（部分参照）できる。
C# 14以前は、明示的に`.AsSpan()`メソッドを呼ぶ必要があったが、C# 14では**暗黙的に変換される**ため、コードがシンプルになり、余計なメモリ割り当てを防ぐことができる。
1. **`ReadOnlySpan<T>`への変換が簡単になった**
C# 14では、`Span<T>`から`ReadOnlySpan<T>`への変換が**暗黙的に行われる**ため、明示的に`.AsReadOnlySpan()`メソッドを呼ぶ必要がなくなった。
1. **文字列(`string`)から`ReadOnlySpan<char>`への変換がスムーズに**
文字列(`string`)は不変(immutable)だが、C# 14では`ReadOnlySpan<char>`に暗黙的に変換できるようになった。
```c#
string text = "Hello, World!";
ReadOnlySpan<char> span = text;  // 暗黙的に変換される
```

# `nameof`演算子での未バインドのジェネリック型のサポート
C# 14では、`nameof`演算子がジェネリック型のオープン型（未バインド型）をサポートするようになりました。​これにより、ジェネリック型の型引数を指定せずに、その型名を取得することが可能となりました。 
```c#
Console.WriteLine(nameof(List<>));  // 出力: List
```
C# 14 より前のバージョンでは、nameof 演算子にジェネリック型を渡す際、クローズド型（型引数が指定された型）のみがサポートされていました。​つまり、型引数を具体的に指定しなければなりませんでした。 
```c#
Console.WriteLine(nameof(List<int>));  // 出力: List
```

### 何がいいの？
型引数に依存せず、より柔軟なコードが書ける


# 修飾子付きラムダ式パラメーターの単純化
C# 14 では、ラムダ式のパラメーターに`scoped`、`ref`、`in`、`out`、`ref`、`readonly`を 型指定なしで追加することが可能になりました。

```c#
delegate bool TryParse<T>(string text, out T result);

TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
```

C# 13以前では、ラムダ式のパラメーターに`out`などの修飾子を付ける場合、パラメーターの型を明示的に記述する必要がありました。
```c#
TryParse<int> parse2 = (string text, out int result) => Int32.TryParse(text, out result);
```

:::note warn
`params`修飾子だけは 型を省略できません。
```c#
// C# 14でもエラーになる
Action printAll = (params values) => Console.WriteLine(string.Join(", ", values));

// 型を明示すればOK
Action<string[]> printAll = (params string[] values) => Console.WriteLine(string.Join(", ", values));
```
:::


# 参考
* https://learn.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-14
* https://www.bytehide.com/blog/whats-new-in-dotnet-10#Whats_New_in_C_14