C#を用いたアプリケーション開発において、オブジェクトの整合性を保つことは非常に重要な課題です。

特に、クラスのプロパティが適切に初期化されていないことによる「意図しないnull参照」や「不完全なデータ状態」は、ランタイムエラーの大きな要因となります。

C# 11で導入されたrequired修飾子は、このような問題をコンパイル時に解決するための強力な機能です。

本記事では、required修飾子の基本的な使い方から、コンストラクタとの関係、継承時の挙動、そして実務における注意点までを詳しく解説します。

required修飾子とは

C# 11(.NET 7)から導入されたrequired修飾子は、クラスや構造体のプロパティおよびフィールドの初期化を呼び出し側に強制するためのキーワードです。

これまでのC#では、特定のプロパティを必ず設定させたい場合、引数付きのコンストラクタを定義するのが一般的でした。

しかし、プロパティの数が増えるにつれてコンストラクタの引数管理が複雑になり、コードの可読性が低下するという問題がありました。

また、オブジェクト初期化子(Object Initializer)を利用する場合、どのプロパティを設定すべきかをコンパイラがチェックする仕組みがありませんでした。

requiredを使用することで、開発者は「このプロパティは絶対に未設定であってはならない」という制約を明示的に宣言でき、設定し忘れた場合にはコンパイルエラーとして即座に通知</cst-redされるようになります。

基本的な使い方と構文

まずは、required修飾子をどのように記述するのか、基本的な構文を見ていきましょう。

プロパティへの適用

プロパティの宣言時に、アクセシビリティ修飾子(publicなど)と型の間にrequiredを記述します。

C#
public class User
{
    // required修飾子を付与
    public required string Id { get; init; }
    
    public required string Name { get; set; }

    // requiredではない通常のプロパティ
    public int Age { get; set; }
}

このクラスを使用する場合、オブジェクト初期化子でIdNameを指定しないと、コンパイルエラーが発生します。

C#
// 正しい初期化
var user1 = new User { Id = "U001", Name = "田中太郎" };

// コンパイルエラー(IdとNameが指定されていない)
// Error CS9035: Required member 'User.Id' must be set in the object initializer or attribute constructor.
var user2 = new User();

このように、オブジェクト生成時に必ず値をセットさせることが可能になります。

これにより、開発者の「うっかりミス」を未然に防ぐことができます。

フィールドへの適用

requiredはプロパティだけでなく、フィールドにも適用可能です。

ただし、モダンなC#開発においてはカプセル化の観点からプロパティに使用するのが一般的です。

C#
public class AppConfig
{
    public required string ApiKey;
}

init専用プロパティとの組み合わせ

requiredは、C# 9で導入されたinitアクセサと非常に相性が良いです。

initは「初期化時のみ値を設定可能にする(その後は読み取り専用)」という機能ですが、これ単体では「必ず設定させる」という強制力はありません。

requiredinitを組み合わせることで、「必ず設定が必要であり、かつ一度設定したら変更できない」という、堅牢なデータモデルを定義できます。

C#
public class Product
{
    // 必須かつ不変(イミュータブル)なプロパティ
    public required string Code { get; init; }
    public required string Name { get; init; }
    public decimal Price { get; init; }
}

コンストラクタとSetsRequiredMembers属性

通常、requiredが付与されたメンバは、呼び出し側がオブジェクト初期化子を使って設定することを想定しています。

しかし、クラス内部のコンストラクタですべての必須メンバを初期化している場合でも、デフォルトではコンパイラは呼び出し側に初期化を要求し続けます。

この挙動を制御するために導入されたのが、SetsRequiredMembers属性です。

属性の役割と実装例

System.Diagnostics.CodeAnalysis名前空間にあるSetsRequiredMembers属性をコンストラクタに付与すると、そのコンストラクタを呼び出す際には呼び出し側のオブジェクト初期化子が不要になります。

C#
using System.Diagnostics.CodeAnalysis;

public class Employee
{
    public required string EmployeeId { get; init; }
    public required string Department { get; init; }

    // デフォルトコンストラクタ(通常は初期化が必要)
    public Employee() { }

    // SetsRequiredMembersを付与したコンストラクタ
    [SetsRequiredMembers]
    public Employee(string id, string dept)
    {
        EmployeeId = id;
        Department = dept;
    }
}

// 実行例
// 1. SetsRequiredMembers付きコンストラクタを使用(初期化子は不要)
var emp1 = new Employee("E001", "開発部");

// 2. デフォルトコンストラクタを使用(初期化子が必要)
var emp2 = new Employee { EmployeeId = "E002", Department = "人事部" };

注意点として、SetsRequiredMembers属性を付与したコンストラクタ内では、すべてのrequiredメンバを適切に初期化する責任が開発者に生じます。

コンパイラはそのコンストラクタ内で本当にすべての必須メンバが代入されたかを厳密にチェックするわけではありません(属性が「初期化した」とコンパイラに報告する役割を果たすため)。

継承におけるrequiredの挙動

クラスを継承する場合、requiredメンバは派生クラスにも引き継がれます。

親クラスで定義された必須プロパティは、子クラスをインスタンス化する際にも必ず指定しなければなりません。

継承のコード例

C#
public class Person
{
    public required string Name { get; init; }
}

public class Student : Person
{
    public required int Grade { get; init; }
}

// Studentを生成する際は、親のNameと自分のGradeの両方が必須
var student = new Student 
{ 
    Name = "佐藤", 
    Grade = 1 
};

もし派生クラスでrequiredメンバをオーバーライドする場合、派生側でもrequiredを付ける必要があります。

親クラスでrequiredだったものを子クラスで「必須ではない」状態に変更することはできません。

逆に、親クラスで必須ではなかったものを子クラスでrequiredにすることは可能です。

他の初期化手法との比較

C#にはオブジェクトの状態を管理するための手法がいくつかあります。

それぞれの特徴を以下の表にまとめました。

機能必須チェックのタイミング読み取り専用化呼び出し側の利便性
コンストラクタコンパイル時可能引数が多いと複雑になる
オブジェクト初期化子なし(任意)initがあれば可能記述が直感的
required修飾子コンパイル時initがあれば可能必須メンバが明確
null許容型 (T?)警告のみ関係なし実行時エラーのリスク

requiredの最大の利点は、コンストラクタの引数地獄(Constructor Over-posting)を回避しつつ、オブジェクト初期化子の柔軟性と型安全性を両立できる点にあります。

実務における活用シーン

DTO(Data Transfer Object)

Web APIのリクエストやレスポンスを定義するDTOでは、特定のフィールドが欠落するとビジネスロジックが破綻することがあります。

C#
public class LoginRequest
{
    public required string Username { get; init; }
    public required string Password { get; init; }
}

このように定義することで、APIのクライアントコードを書く際や、テストコードを作成する際に、必要なデータの入れ忘れを防ぐことができます。

Entity Framework Core (EF Core)

EF Coreのエンティティクラスにおいても、データベースのNOT NULL制約があるカラムに対応するプロパティにrequiredを付与することで、メモリ上でのオブジェクト生成段階で整合性を担保できます。

ただし、EF Coreが内部的にインスタンスを生成する際にデフォルトコンストラクタを使用するため、一部のバージョンや設定によっては注意が必要です。

基本的には、EF Core 7以降であればrequiredメンバを適切にサポートしています。

注意点と制限事項

非常に便利なrequiredですが、いくつか制約もあります。

  1. インターフェースには使用不可
    インターフェース内でrequiredプロパティを定義することはできません。
  2. リフレクションやシリアライザの影響
    一部の古いJSONシリアライザやリフレクションを用いたライブラリでは、requiredによる制約を無視したり、逆に初期化が困難になったりする場合があります。System.Text.Jsonなどの最新のライブラリであれば問題なく動作します。
  3. 構造体の引数なしコンストラクタ
    C# 10から構造体でも引数なしコンストラクタを定義できるようになりましたが、requiredを組み合わせる際は挙動が複雑になるため、意図した通りにチェックが働くか確認が必要です。

サンプルプログラムと実行結果

最後に、ここまでの内容をまとめた実践的なサンプルプログラムを示します。

C#
using System;
using System.Diagnostics.CodeAnalysis;

namespace RequiredModifierDemo
{
    public class Book
    {
        // 必須項目
        public required string Isbn { get; init; }
        public required string Title { get; init; }
        
        // 任意項目
        public string? Author { get; init; }

        public Book() { }

        // 特定のケースではコンストラクタで完結させる
        [SetsRequiredMembers]
        public Book(string isbn, string title)
        {
            Isbn = isbn;
            Title = title;
        }

        public void DisplayInfo()
        {
            Console.WriteLine($"ISBN: {Isbn}, Title: {Title}, Author: {Author ?? "不明"}");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // 1. オブジェクト初期化子での生成
            var book1 = new Book
            {
                Isbn = "978-4-0000-0000-0",
                Title = "C#実践ガイド",
                Author = "技術 太郎"
            };
            book1.DisplayInfo();

            // 2. SetsRequiredMembers付きコンストラクタでの生成
            var book2 = new Book("978-4-1111-1111-1", "プログラミングの基礎");
            book2.DisplayInfo();

            // 以下のコードはコンパイルエラーになる(Titleがないため)
            // var book3 = new Book { Isbn = "123-456" };
        }
    }
}
実行結果
ISBN: 978-4-0000-0000-0, Title: C#実践ガイド, Author: 技術 太郎
ISBN: 978-4-1111-1111-1, Title: プログラミングの基礎, Author: 不明

このプログラムでは、requiredによってオブジェクトの不完全な生成を防止しつつ、柔軟な初期化方法を提供していることがわかります。

まとめ

C#のrequired修飾子は、従来のコンストラクタによる制約と、オブジェクト初期化子の利便性を絶妙に融合させた機能です。

  • プロパティやフィールドの初期化を呼び出し側に強制し、ランタイムエラーを未然に防ぐ。
  • initアクセサと組み合わせることで、「必須かつ不変」なデータ構造を簡単に構築できる。
  • SetsRequiredMembers属性により、コンストラクタベースの初期化とも柔軟に併用可能。

大規模なプロジェクトや、多くの開発者が関わるライブラリ開発において、requiredは「コードによる契約」を明確にするための不可欠なツールと言えるでしょう。

これから新規で作成するクラスや、リファクタリングを検討している既存のデータモデルにおいて、ぜひ積極的に活用してみてください。