6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# 2026年のプロパティ宣言まとめ——fieldキーワード・init・required・プライマリコンストラクタ

6
Posted at

TL;DR

  • C# 14(.NET 10)の field キーワードで、バッキングフィールドを手動宣言せずにカスタムアクセサを書ける
  • init(C# 9)で初期化後に変更不可のプロパティ、required(C# 11)で初期化必須のプロパティを宣言できる
  • プライマリコンストラクタ(C# 12)で通常クラスのDIボイラープレートを削減できる
  • 2026年時点では手動バッキングフィールドが必要なケースは限定的になった

環境

項目 バージョン
.NET .NET 10
C# 14
IDE Visual Studio 2026 / Rider 2025.3以降

1. 旧来のバッキングフィールドパターン

バリデーションが必要なプロパティは、従来プライベートフィールドを手動宣言してアクセサから操作していた。

public class User
{
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("名前は空にできません");
            _name = value;
        }
    }
}

クラスが大きくなると、フィールドとプロパティの対応関係を目で追う必要があり、可読性が低下しやすい。


2. C# 14: field キーワード

.NET 10 (C# 14) で追加。コンパイラが生成するバッキングフィールドに field という識別子でアクセスできる。プライベートフィールドの手動宣言が不要になる。

public class User
{
    public string Name
    {
        get => field;
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("名前は空にできません");
            field = value;
        }
    }
}

get だけ / set だけカスタムにする

よく使うパターンとして、set だけロジックを入れて get はオートプロパティのままにできる。

public string Email
{
    get;
    set
    {
        if (!value.Contains('@'))
            throw new FormatException("メールアドレスの形式が不正です");
        field = value.ToLowerInvariant();  // 小文字に正規化して保存
    }
}

既存の field 識別子との衝突

クラス内に field という名前のメンバーがある場合、キーワードと衝突する。@field(verbatim identifier)で既存メンバーを参照できる。

public class Example
{
    private string field = "existing";  // 既存のフィールド

    public string Value
    {
        get => @field;  // 既存フィールドを参照
        set => @field = value;
    }
}

3. C# 9: init アクセサ

コンストラクタ・オブジェクト初期化子での書き込みのみを許可し、それ以降は読み取り専用になる。set の代わりに init と書く。

public class Order
{
    public Guid Id { get; init; } = Guid.NewGuid();
    public DateTime CreatedAt { get; init; } = DateTime.UtcNow;
    public string CustomerId { get; init; }
}

var order = new Order { CustomerId = "C001" };  // OK
order.CustomerId = "C002";                       // CS8852 コンパイルエラー

readonly フィールドと同じ不変性をプロパティとして表現できる。レコード型と組み合わせることが多いが、通常クラスでも使える。


4. C# 11: required モディファイア

オブジェクト初期化子でそのプロパティを設定しなければコンパイルエラー (CS9035) になる。コンストラクタを書かずに初期化の強制ができる。

public class Product
{
    public required string Name { get; init; }
    public required decimal Price { get; init; }
    public string? Description { get; init; }  // 任意
}

// Name を省略するとコンパイルエラー
var p1 = new Product { Price = 1000 };  // CS9035

// 正しい使い方
var p2 = new Product { Name = "コーヒー豆", Price = 1000 };

DTOやモデルクラスで特に有用。[SetsRequiredMembers] 属性をコンストラクタに付けると、そのコンストラクタ経由での初期化を required 検査から除外できる。

public class Product
{
    public required string Name { get; init; }
    public required decimal Price { get; init; }

    [SetsRequiredMembers]
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }
}

var p = new Product("コーヒー豆", 1000);  // コンストラクタ経由はOK

5. C# 12: プライマリコンストラクタ(通常クラス)

C# 9のレコード型に限定されていたプライマリコンストラクタが、通常のクラスとstructにも解放された。DIでよくある「コンストラクタでサービスを受け取ってフィールドに代入する」パターンを大幅に短縮できる。

// C# 12 以前
public class OrderService
{
    private readonly IOrderRepository _repository;
    private readonly ILogger<OrderService> _logger;

    public OrderService(IOrderRepository repository, ILogger<OrderService> logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public async Task<Order> GetAsync(Guid id)
    {
        _logger.LogInformation("Getting order {Id}", id);
        return await _repository.GetByIdAsync(id);
    }
}

// C# 12 以降
public class OrderService(IOrderRepository repository, ILogger<OrderService> logger)
{
    public async Task<Order> GetAsync(Guid id)
    {
        logger.LogInformation("Getting order {Id}", id);
        return await repository.GetByIdAsync(id);
    }
}

レコード型との違い

挙動 record class/struct
パラメータがプロパティになる ✓(自動生成) ✗(スコープ内変数のまま)
外部からパラメータを参照 別途プロパティ定義が必要

通常クラスのプライマリコンストラクタパラメータは、クラスボディ全体で参照できる変数であってプロパティではない。外部に公開したい場合は明示的にプロパティを定義する。

public class Config(string connectionString)
{
    // 外部公開したい場合はプロパティを定義
    public string ConnectionString { get; } = connectionString;
}

2026年時点での使い分け

パターン 使う場面
{ get; set; } バリデーションなしの単純なプロパティ
{ get; init; } 初期化後に変更させたくないプロパティ
required { get; init; } 初期化必須 + 変更不可。DTOやモデル
field キーワード setにバリデーション・変換が必要。バッキングフィールド不要
バッキングフィールド手動宣言 フィールドを他のメソッドや属性からも参照する場合

field キーワードの追加で、手動バッキングフィールドが必要なケースは「フィールドをアクセサ外のメソッドから直接参照したい」「シリアライズ属性をフィールドに付けたい」などに限定された。


まとめ

  • field(C# 14): バッキングフィールド手動宣言が不要。カスタムアクセサの標準的な書き方になりつつある
  • init(C# 9): 初期化後のイミュータビリティをプロパティレベルで保証
  • required(C# 11): 初期化漏れをコンパイル時に検出。コンストラクタ不要でDTOが書ける
  • プライマリコンストラクタ(C# 12): DI設定のボイラープレートを削減

概要・個人的な感想はこちら → C# 2026年のプロパティ宣言——fieldキーワードとget/setの書き方まとめ

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?