クラスのプロパティとアクセサーの概要

完了

クラス プロパティは、データ フィールドの値を読み取り、書き込み、または計算するための柔軟なメカニズムを提供します。 これらはパブリック データ メンバーとして表示されますが、アクセサーと呼ばれる特別なメソッドとして実装。 この機能により、呼び出し元はデータに簡単にアクセスでき、データの安全性と柔軟性を高めるのに役立ちます。

プロパティの使用

プロパティは、フィールドとメソッドの両方の側面を結合します。 オブジェクトのユーザーには、プロパティがフィールドのように見えます。 プロパティにアクセスするには、フィールドへのアクセスと同じ構文が必要です。 クラスの実装者には、プロパティは 1 つまたは 2 つのコード ブロックであり、get アクセサー、set、または init アクセサーを表します。 get アクセサーのコード ブロックは、プロパティの読み取り時に実行されます。 プロパティに値が割り当てられると、set または init アクセサーのコード ブロックが実行されます。 set アクセサーのないプロパティは、読み取り専用と見なされます。 get アクセサーのないプロパティは、書き込み専用と見なされます。 両方のアクセサーを持つプロパティは、読み取り/書き込みです。 init アクセサーの代わりに set アクセサーを使用して、プロパティをオブジェクト初期化の一部として設定できますが、それ以外の場合は読み取り専用にすることができます。

フィールドとは異なり、プロパティは変数として分類されません。 そのため、プロパティを ref または out パラメーターとして渡すことはできません。

プロパティは、多くの場合、次のシナリオをサポートするために使用されます。

  • 変更を許可する前にデータを検証できます。
  • データベースなどの他のソースからデータを取得するクラス上のデータを透過的に公開できます。
  • イベントの発生や他のフィールドの値の変更など、データが変更されたときにアクションを実行できます。

プロパティは、クラス定義のコード ブロックで宣言されます。 プロパティは、フィールドのアクセス レベル、プロパティの型、プロパティの名前、get アクセサーまたは set アクセサーを宣言するコード ブロックを指定することによって宣言されます。

get アクセサー

get アクセサーの本体は、メソッドの本体に似ています。 プロパティ型の値を返す必要があります。 C# コンパイラと Just-In-Time (JIT) コンパイラは、get アクセサーを実装するための一般的なパターンを検出し、それらのパターンに合わせて最適化します。

たとえば、計算を実行せずにフィールドを返す get アクセサーは、そのフィールドのメモリ読み取りに最適化されている可能性があります。 このモジュールの次のユニットで確認したプロパティを自動的に実装し、このパターンに従い、これらの最適化のメリットを得られます。 ただし、コンパイル時にコンパイラで実行時に実際に呼び出される可能性があるメソッドが認識されないため、仮想 get アクセサー メソッドをインライン化することはできません。

次の例は、プライベート フィールド getの値を返す _name アクセサーを示しています。


public class Employee
{
    private string _name = "unknown";  // the name field
    public string Name
    {
        get { return _name; }  // the Name property
    }
}

この例では、Employee クラスにプライベート フィールドとパブリック プロパティが含まれています。 Employee クラス定義の説明を次に示します。

  • Employee クラスはパブリック アクセス修飾子を使用して定義されます。つまり、同じアセンブリ内の他のコードまたはそれを参照する別のアセンブリからアクセスできます。
  • プライベート フィールド _name は、string 型で宣言され、既定値 "unknown"割り当てられます。
  • パブリック プロパティ Name は、string 型で宣言されます。 このプロパティには、get フィールドの値を返す _name アクセサーが含まれています。

手記

プロパティ アクセサーの値を提供するフィールドは、多くの場合、バッキング フィールドと呼ばれます。

Employee オブジェクトをインスタンス化し、Name プロパティの値を読み取るコードについて考えてみましょう。

Employee employee = new Employee();  // create an instance of the Employee class

Console.Write(employee.Name);  // use the get accessor to read the value of the Name property

このコード サンプルでは、Employeeという名前の新しい employee オブジェクトをインスタンス化します。 コードが割り当てのターゲットを除き、employee.Name プロパティを参照すると、プロパティの値を読み取るために get アクセサーが呼び出されます。 この場合、get アクセサーは、_name フィールドの値を返します。"unknown"値が割り当てられます。

get アクセサーのコード ブロックを使用して、計算値を返すこともできます。 これは、プロパティが常に null 以外の値を返すようにする場合に便利です。 例えば:


public class Manager
{
    private string? _name;
    public string Name
    {
        get
        {
            return _name != null ? _name : "NA";
        }
    }
}

set アクセサー

set アクセサーは、戻り値の型が void であるメソッドに似ています。 valueという暗黙的なパラメーターを使用します。このパラメーターの型はプロパティの型です。 コンパイラと JIT コンパイラは、set または init アクセサーの一般的なパターンも認識します。 これらの一般的なパターンが最適化され、バッキング フィールドのメモリが直接書き込まれます。 次の例では、set アクセサーが Name プロパティに追加されています。


class Student
{
    private string? _name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return _name != null ? _name : "NA";
        }
        set
        {
            _name = value;
        }
    }
}

Student クラスのインスタンスを作成するコードについて考えてみましょう。 Name プロパティに値を割り当てると、set アクセサーは、新しい値を提供する引数を使用して呼び出されます。 例えば:


var student = new Student();
student.Name = "StudentName";  // the set accessor is invoked here

Console.Write(student.Name);  // the get accessor is invoked here

init アクセサー

init アクセサーを作成するコードは、setではなく init キーワードを使用する点を除き、set アクセサーを作成するコードと同じです。 違いは、init アクセサーは、コンストラクター内でのみ、または オブジェクト初期化子を使用して使用できることです。

読み取り/書き込みプロパティを宣言して使用する

プロパティは、オブジェクトのデータへの保護されていない、制御されていない、および確認されていないアクセスに伴うリスクを伴わない、パブリック データ メンバーの利便性を提供します。 プロパティはアクセサーを宣言します。基になるデータ メンバーに値を割り当てて取得する特殊なメソッドです。 set アクセサーを使用すると、データ メンバーを割り当て、get アクセサーはデータ メンバーの値を取得します。

このサンプルでは、Person (文字列) と Name (int) の 2 つのプロパティを持つ Age クラスを示します。 どちらのプロパティも get アクセサーと set アクセサーを提供するため、読み取り/書き込みプロパティと見なされます。


class Person
{
    private string _name = "N/A";
    private int _age = 0;

    // Declare a Name property of type string:
    public string Name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }

    // Declare an Age property of type int:
    public int Age
    {
        get
        {
            return _age;
        }
        set
        {
            _age = value;
        }
    }
}

class TestPerson
{
    static void Main()
    {
        // Create a new Person object named person:
        Person person = new Person();

        // Print out the default name and age of the person:
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Set some values on the person object:
        person.Name = "PersonName";
        person.Age = 99;
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Increment the Age property:
        person.Age += 1;
        Console.WriteLine($"Person details - Name = {person.Name}, Age = {person.Age}");

        // Keep the console window open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}

/* Output:
Person details - Name = N/A, Age = 0
Person details - Name = PersonName, Age = 99
Person details - Name = PersonName, Age = 100
*/

アクセサーの構文とコーディング手法

C# には、プロパティを宣言するための基本的な構文に加えて、より簡潔で表現力豊かなコードを記述できる構文が含まれています。 次のような機能があります。

  • 式形式のメンバー
  • フィールドに基づくプロパティ
  • 必須プロパティ

式形式のメンバー

式形式のメンバーは、単一行のプロパティ アクセサーを記述するためのより簡潔な構文を提供します。

手記

式形式のメンバーは、メソッド、インデクサー、およびイベント アクセサーにも適用できます。

プロパティ アクセサーは、多くの場合、式の結果を代入または返す単一行ステートメントで構成されます。 このユニットで前に調べた Person クラスは、良い例です。


class Student
{
    private string? _name;  // the name field
    public string Name    // the Name property
    {
        get
        {
            return _name != null ? _name : "NA";
        }
        set
        {
            _name = value;
        }
    }
}

この例では、Name プロパティは単一行ステートメントを使用して、_name フィールドの値が null でない場合は値を返し、null の場合は文字列 "NA" を返します。 set アクセサーは、単一行ステートメントを使用して、Name プロパティの値を _name フィールドに割り当てます。

式本体の定義は、=> トークンの後に、プロパティ値の割り当てまたは取得に使用される式で構成されます。 Student クラスに定義されているアクセサーは単一行ステートメントを実装するため、式本体の定義を使用して更新することをお勧めします。

次のコード スニペットは、Student アクセサーと get アクセサーの式本体定義を使用して、set クラスを更新します。


class Student
{
    private string? _name;  // the name field
    public string Name    // the Name property
    {
        get => _name ?? "NA";
        set => _name = value;
    }
}

フィールドに基づくプロパティ

C# 13 以降では、field キーワード プレビュー機能を使用して、プロパティのアクセサーに検証またはその他のロジックを追加できます。 field キーワードは、プロパティのコンパイラ合成バッキング フィールドにアクセスします。 これにより、個別のバッキング フィールドを明示的に宣言せずに、プロパティ アクセサーを記述できます。


public class Person
{
    public string? FirstName 
    { 
        get;
        set => field = value.Trim(); 
    }

    // Omitted for brevity.
}

大事な

field キーワードは、C# 13 のプレビュー機能です。 <LangVersion> コンテキスト キーワードを使用するには、.NET 9 を使用し、プロジェクト ファイルでプレビューする field 要素を設定する必要があります。

fieldという名前のフィールドを持つクラスでは、field キーワード機能の使用に注意する必要があります。 新しい field キーワードは、プロパティ アクセサーのスコープ内の field という名前のフィールドをシャドウします。 field 変数の名前を変更するか、@ トークンを使用してフィールド識別子を @fieldとして参照できます。

必須プロパティ

前の例では、呼び出し元は、Person プロパティを設定せずに、既定のコンストラクターを使用して FirstName を作成できます。 プロパティ型は null 許容文字列に設定されます。 C# 11 以降では、呼び出し元にプロパティの設定を要求できます。


public class Person
{
    public Person() { }

    [SetsRequiredMembers]
    public Person(string firstName) => FirstName = firstName;

    public required string FirstName { get; init; }

    // Omitted for brevity.
}

上記のコードは、Person クラスに 2 つの変更を加えます。 最初に、FirstName プロパティ宣言には、required 修飾子が含まれます。 つまり、新しい Person を作成するコードでは、オブジェクト初期化子を使用してこのプロパティを設定する必要があります。 次に、firstName パラメーターを受け取るコンストラクターには、System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 属性があります。 この属性は、このコンストラクターが必要なすべてのメンバーを設定することをコンパイラに通知します。 このコンストラクターを使用する呼び出し元は、オブジェクト初期化子 required プロパティを設定する必要はありません。

大事な

required と null 非許容を混同しないでください。 required プロパティを null または defaultに設定すると有効です。 これらの例の string など、型が null 非許容の場合、コンパイラは警告を発行します。


var aPerson = new Person("PersonName");
aPerson = new Person{ FirstName = "PersonName"};
// Error CS9035: Required member `Person.FirstName` must be set:
//aPerson2 = new Person();