3/6に.NET公式Xで、次期LTS版となる.NET 10 Preview1のリリースが発表され、なんか盛り上がってるぽい。
気になったので、C# 14の新機能を調べてみました。
間違ってたらご指摘いただけますと幸いです。
目次
field
キーワード
field
キーワードは、プロパティのアクセサ内でコンパイラが生成するバックフィールドにアクセスするためのコンテキストキーワードです。これにより、明示的にバックフィールドを宣言することなく、プロパティのget
およびset
アクセサ内で直接そのフィールドを操作できます。
従来、プロパティに対してカスタムのロジックを追加する際には、明示的にバックフィールドを宣言し、get
およびset
アクセサを実装する必要がありました。例えば、null
値の設定を防ぐためには、以下のように記述していました。
private string _message;
public string Message
{
get => _message;
set => _message = value ?? throw new ArgumentNullException(nameof(value));
}
C# 14では、field
キーワードを使用することで、プロパティの自動実装と同様に簡潔に記述しつつ、カスタムロジックを組み込むことが可能となりました。上記の例は以下のように簡略化できます。
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
このように、set
アクセサ内でfield
キーワードを使用して、null
値の設定を防ぐロジックを直接記述できます。
何がいいの?
-
バックフィールドを明示せずにプロパティのロジックを記述できる
上の例でいうと、_message
を記述する必要がなくなったことにより可読性が向上。自動プロパティとの統一性も保たれる。 -
バックフィールドの名前衝突を防げる
従来の方法では、開発者が_messag
や_value
などのバックフィールド名を自分で管理する必要があった。
しかし、C# 14のfield
は、コンパイラが自動生成するバックフィールドを参照するため、フィールド名の衝突を防ぐことができる。 -
保守性の向上
ルールを直接プロパティに書けるので、保守しやすい。
要するに、field
は「シンプルなコードでプロパティにロジックを追加したい」場合に特に有用!
field
キーワードの導入により、既存のコードでfield
という名前のフィールドが存在する場合、名前の衝突が発生する可能性があります。そのような場合、@field
またはthis.field
として参照することで曖昧さを解消できます。
補足:C# 13、,.NET 9でもプレビュー機能として使えるとのことです。
暗黙的なスパン変換
C# 14では、System.Span<T>
およびSystem.ReadOnlySpan<T>
に対する暗黙的な型変換が強化され、これらの型をより自然に操作できるようになりました。これにより、パフォーマンス向上と安全性の確保が期待できます。
Span<int> numbers = new int[] { 1, 2, 3 };
ReadOnlySpan<int> readOnlyNumbers = numbers;
このように、T[]
やList<T>
からSpan<T>
への変換や、Span<T>
からReadOnlySpan<T>
への変換が容易になりました。
何がいいの?
-
メモリ割り当てを減らし、パフォーマンスを向上
Span<T>
はヒープアロケーションを行わない構造体(struct
)であり、配列やList<T>
の一部を安全にスライス(部分参照)できる。
C# 14以前は、明示的に.AsSpan()
メソッドを呼ぶ必要があったが、C# 14では暗黙的に変換されるため、コードがシンプルになり、余計なメモリ割り当てを防ぐことができる。 -
ReadOnlySpan<T>
への変換が簡単になった
C# 14では、Span<T>
からReadOnlySpan<T>
への変換が暗黙的に行われるため、明示的に.AsReadOnlySpan()
メソッドを呼ぶ必要がなくなった。 -
文字列(
string
)からReadOnlySpan<char>
への変換がスムーズに
文字列(string
)は不変(immutable)だが、C# 14ではReadOnlySpan<char>
に暗黙的に変換できるようになった。
string text = "Hello, World!";
ReadOnlySpan<char> span = text; // 暗黙的に変換される
nameof
演算子での未バインドのジェネリック型のサポート
C# 14では、nameof
演算子がジェネリック型のオープン型(未バインド型)をサポートするようになりました。これにより、ジェネリック型の型引数を指定せずに、その型名を取得することが可能となりました。
Console.WriteLine(nameof(List<>)); // 出力: List
C# 14 より前のバージョンでは、nameof 演算子にジェネリック型を渡す際、クローズド型(型引数が指定された型)のみがサポートされていました。つまり、型引数を具体的に指定しなければなりませんでした。
Console.WriteLine(nameof(List<int>)); // 出力: List
何がいいの?
型引数に依存せず、より柔軟なコードが書ける
修飾子付きラムダ式パラメーターの単純化
C# 14 では、ラムダ式のパラメーターにscoped
、ref
、in
、out
、ref
、readonly
を 型指定なしで追加することが可能になりました。
delegate bool TryParse<T>(string text, out T result);
TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
C# 13以前では、ラムダ式のパラメーターにout
などの修飾子を付ける場合、パラメーターの型を明示的に記述する必要がありました。
TryParse<int> parse2 = (string text, out int result) => Int32.TryParse(text, out result);
params
修飾子だけは 型を省略できません。
// C# 14でもエラーになる
Action printAll = (params values) => Console.WriteLine(string.Join(", ", values));
// 型を明示すればOK
Action<string[]> printAll = (params string[] values) => Console.WriteLine(string.Join(", ", values));