【C#】C# 14 逆引き — field・拡張メンバー・null条件代入

C# 14 逆引きを表したアイキャッチ画像

C# 14で追加されたfieldキーワード・拡張メンバー・null条件代入の書き方を、.NET 10で実行した結果つきで逆引き形式にまとめます。nameof(List<>)やSpanの暗黙変換も実測で確認できます。

目次

この記事が解決する悩み

  • C# 14 の新機能、名前は見かけたけど実際どの書き方で使うのか分からない
  • バッキングフィールドや、既存クラスへの後付け拡張を毎回調べ直している
  • .NET 10 に上げたのに C# 14 の新構文をまだ1つも使えていない

対象読者は、C# を業務で書いていて C# 12〜13 までは把握しているエンジニアです。

プロパティ・拡張メソッド・Span<T>・ラムダの基本は知っている前提で進めます。

結論

以下がまとめです。すべて .NET 10 の C# 14 で動くことを確認済みです。

// 1. バッキングフィールドを書かずに検証する
public string Name { get; set => field = value.Trim(); }

// 2. 既存の型にプロパティや演算子を後から足す
extension(Point p)
{
    public double Length => Math.Sqrt((p.X * p.X) + (p.Y * p.Y));
}

// 3. null かもしれない相手に安全に代入する
maybe?.Level = 9;

// 4. ジェネリック型の名前を nameof で取る
nameof(List<>)                       // "List"

// 5. 配列を Span 引数にそのまま渡す
Sum(numbers);                        // int[] -> ReadOnlySpan<int>

// 6. 型を書かずに out 付きラムダを書く
TryParse<int> parse = (text, out value) => int.TryParse(text, out value);

以下、1つずつ実測結果つきで見ていきます。

1. バッキングフィールドを書かずにプロパティを検証するには

field キーワードを使うと、プロパティの裏にある自動生成フィールドをそのまま参照できます。

// ---- 1. field キーワード ----
public class Member
{
    public Member()
    {
        Name = "名無し";
    }

    public string Name
    {
        get;
        set => field = string.IsNullOrWhiteSpace(value) ? "名無し" : value.Trim();
    }

    public int Level
    {
        get => field;
        set
        {
            if (value < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }

            field = value;
        }
    }
}

var m = new Member();
m.Name = "   ";
m.Level = 3;
Console.WriteLine($"1: [{m.Name}] {m.Level}");

// 実行結果
// 1: [名無し] 3

get; と併用できるので、片方だけ自動実装のまま残せます。

空白だけの入力を既定値に落とす処理が、これだけで書けます。

2. 既存の型にプロパティや演算子を後から足すには

拡張メンバーは、拡張メソッドと同じ発想をプロパティと演算子に広げた機能です。

// ---- 2. 拡張メンバー ----
public readonly record struct Point(double X, double Y);

public static class PointExtensions
{
    extension(Point p)
    {
        public double Length => Math.Sqrt((p.X * p.X) + (p.Y * p.Y));
        public static Point Origin => new Point(0, 0);
        public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y);
    }

    extension<T>(IEnumerable<T> source)
    {
        public bool IsEmpty => !source.Any();
    }
}

Point a = new Point(3, 4);
Point b = new Point(1, 2);
Console.WriteLine($"2: {a.Length} {Point.Origin} {a + b} {new[] { 1 }.IsEmpty}");

// 実行結果
// 2: 5 Point { X = 0, Y = 0 } Point { X = 4, Y = 6 } False

拡張ブロックは extension(レシーバー) で始まり、その中にメソッド・プロパティ・演算子・静的メンバーを書きます。

元の型に手を入れずに a + b を定義できるのが、拡張メソッドとの大きな違いです。

3. null かもしれないオブジェクトに安全に代入するには

null 条件演算子 ?. が、C# 14 から代入の左辺でも使えます。

Member? maybe = null;
maybe?.Level = 9;
Console.WriteLine($"3a: {maybe is null}");

maybe = new Member();
maybe?.Level = 9;
Console.WriteLine($"3b: {maybe!.Level}");

// 実行結果
// 3a: True
// 3b: 9

相手が null のときは何も起きず、例外にもなりません。

if 文で囲む必要がなくなるので、初期化処理の見通しが良くなります。

4. ジェネリック型の名前を nameof で取るには

未バインドのジェネリック型を nameof に渡せるようになりました。

Console.WriteLine($"4: {nameof(List<>)} {nameof(Dictionary<,>)} {nameof(Point)}");

// 実行結果
// 4: List Dictionary Point

型引数を書かずに済むので、ログや例外メッセージに型名を埋め込むのが楽になります。

5. 配列を Span 引数にそのまま渡すには

配列・Span<T>・ReadOnlySpan<T> の間の変換が、言語組み込みの暗黙変換になりました。

private static int Sum(ReadOnlySpan<int> values)
{
    int total = 0;

    foreach (int v in values)
    {
        total += v;
    }

    return total;
}

private static string Describe<T>(ReadOnlySpan<T> values) => $"len={values.Length}";

int[] numbers = { 1, 2, 3 };
Span<int> span = numbers;
ReadOnlySpan<int> ro = span;
Console.WriteLine($"5: {Sum(numbers)} {ro.Length} {Describe(numbers)}");

// 実行結果
// 5: 6 3 len=3

オーバーロード解決だけでなく、ジェネリックメソッドの型推論でも効きます。

Describe<T>(ReadOnlySpan<T>) に int[] をそのまま渡して T が int に決まるのは、C# 14 からです。

6. 型を書かずに out 付きラムダを書くには

ラムダのパラメータに out などの修飾子を付けても、型を省略できるようになりました。

private delegate bool TryParse<T>(string text, out T value);

TryParse<int> parse = (text, out value) => int.TryParse(text, out value);
Console.WriteLine($"6: {parse("42", out int n)} {n}");

// 実行結果
// 6: True 42

型はデリゲート型から決まるので、out int value と書き分ける必要がありません。

つまずきポイント

  • 拡張ブロックにフィールド・インデクサ・イベントは置けない。コンパイラは CS9282「このメンバーは拡張ブロックでは使用できません」を出す
  • 静的拡張メンバーは拡張クラス名から呼べない。PointExtensions.Origin は CS1929 になり、Point.Origin と書く必要がある
  • field を使った非 null 許容プロパティをコンストラクタで初期化しないと CS9264 が出る
  • maybe?.Level = 9; の後でもコンパイラは maybe を null かもしれない扱いにする。maybe!.Level と書かないと CS8602 になる
  • LangVersion 13 では CS9260「機能 ‘field キーワード’ は C# 13.0 では使用できません。14.0 以上の言語バージョンを使用してください」が出る

検証環境

OS Windows 11 (build 10.0.26200)
CPU AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics
メモリ 28GB
言語/ツール .NET SDK 10.0.201 / C# 14.0 / net10.0
使用コマンド dotnet –list-sdks / dotnet build / dotnet run –no-build
測定日 2026-09-19

各セクションに載せているコードと実行結果は、すべてこの環境で実際に実行したものです。環境が異なると挙動が変わることがあります。

C# 14 の field・拡張メンバー・null 条件代入・未バインドジェネリック型の nameof・Span の暗黙変換・out 付きラムダを .NET 10(LangVersion 14.0)で実行した結果と、拡張ブロックに置けないメンバー(CS9282)・静的拡張メンバーの誤った呼び方(CS1929)・LangVersion 13 での拒否(CS9260)のエラー
C# 14 の field・拡張メンバー・null 条件代入・未バインドジェネリック型の nameof・Span の暗黙変換・out 付きラムダを .NET 10(LangVersion 14.0)で実行した結果と、拡張ブロックに置けないメンバー(CS9282)・静的拡張メンバーの誤った呼び方(CS1929)・LangVersion 13 での拒否(CS9260)のエラー

まとめ

用途別にまとめます。

やりたいこと 書くもの 備考
プロパティの値を検証する field get; と併用できる
型に後付けでプロパティ・演算子を足す extension(レシーバー) { } フィールド・インデクサ・イベントは不可
null かもしれない相手に代入する ?. = null なら何もしない
ジェネリック型の名前を取る nameof(List<>) 型引数は書かない
配列を Span 引数へ渡す そのまま渡す 型推論にも効く
out 付きラムダを書く (text, out value) => 型はデリゲートから決まる

C# 14 は、これまで書きづらかった場所を埋める機能が中心です。

まずは field と ?. の2つから試すのがおすすめですよ。

【広告】お名前.comならドメイン取得が格安。

参考

本文の記述は以下の一次情報を確認して書いています。

あわせて読みたい

【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次