プロパティの使用 (C# プログラミング ガイド)

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

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

プロパティには多くの用途があります。

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

プロパティはクラス ブロックで宣言できます。フィールドのアクセス レベル、プロパティの型、プロパティの名前、get アクセサーと set アクセサーの両方またはいずれかを宣言するコード ブロックの順で指定します。 次に例を示します。

public class Date
{
    private int _month = 7;  // Backing store

    public int Month
    {
        get => _month;
        set
        {
            if ((value > 0) && (value < 13))
            {
                _month = value;
            }
        }
    }
}

この例では、set アクセサーが Month が 1 から 12 までの値に設定されていることを確認できるように、Month がプロパティとして宣言されています。 Month プロパティは、プライベート フィールドを使用して実際の値を追跡します。 プロパティのデータの実際の場所は、プロパティの "バッキング ストア" と呼ばれることがよくあります。プロパティがプライベート フィールドをバッキング ストアとして使用するのは一般的なことです。 フィールドは、プロパティを呼び出すことでのみ変更できるようにするため、プライベートとマークされます。 パブリックおよびプライベートのアクセス制限の詳細については、「アクセス修飾子」を参照してください。 自動実装プロパティは、単純なプロパティ宣言の簡単な構文を提供します。 詳細については、「自動実装プロパティ」を参照してください。

get アクセサー

get アクセサーの本体は、メソッドの本体と似ています。 プロパティの型の値を返す必要があります。 C# コンパイラと Just-In-Time (JIT) コンパイラは、get アクセサーを実装するための一般的なパターンを検出し、それらのパターンを最適化します。 たとえば、計算を実行せずにフィールドを返す get アクセサーは、そのフィールドのメモリ読み取りに最適化されている可能性があります。 自動実装プロパティは、このパターンに従い、これらの最適化からメリットを得ます。 ただし、仮想 get アクセサー メソッドはインライン化できません。これは、コンパイラがコンパイル時にどのメソッドが実際に実行時に呼び出されるかを認識しないからです。 次に、プライベート フィールド _name の値を返す get アクセサーの例を示します。

class Employee
{
    private string _name;  // the name field
    public string Name => _name;     // the Name property
}

プロパティを参照するとき、割り当ての対象を除き、get アクセサーがプロパティの値を読み取るために呼び出されます。 次に例を示します。

var employee= new Employee();
//...

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

get アクセサーは式形式のメンバーであるか、return または throw ステートメントで終わる必要があり、コントロールはアクセサー本体からフロー オフすることはできません。

警告

get アクセサーを使用してオブジェクトの状態を変更するのは、悪いプログラミング スタイルです。

get アクセサーは、フィールド値を返すまたは計算してから返すために使用できます。 次に例を示します。

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

前の例では、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 => _name;
        set => _name = value;
    }
}

プロパティに値を割り当てるときに、新しい値を提供する引数を使用して set アクセサーが呼び出されます。 次に例を示します。

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

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

set アクセサーでローカル変数の宣言に暗黙のパラメーター名 value を使用すると、エラーになります。

init アクセサー

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

解説

プロパティは publicprivateprotectedinternalprotected internalprivate protected のいずれかでマークできます。 これらのアクセス修飾子により、クラスのユーザーがプロパティにアクセスできる方法が定義されます。 同じプロパティの getset アクセサーは、異なるアクセス修飾子を持つことができます。 たとえば、getpublic にして、型の外部からの読み取り専用アクセスを許可して、setprivate または protected にすることができます。 詳細については、「アクセス修飾子」を参照してください。

static キーワードを使用して、プロパティを静的プロパティとして宣言できます。 静的プロパティは、クラスのインスタンスが存在しなくても、呼び出し元でいつでもを使用できます。 詳細については、「静的クラスと静的クラス メンバー」を参照してください。

プロパティは、virtual キーワードを使用して仮想プロパティとしてマークできます。 仮想プロパティを使うと、派生クラスで override キーワードを使ってプロパティの動作をオーバーライドできます。 これらのオプションの詳細については、「継承」を参照してください。

仮想プロパティをオーバーライドするプロパティは、sealed にすることもできます。その場合、派生クラスでは、プロパティが仮想でなくなります。 最後に、プロパティは抽象として宣言できます。 抽象プロパティではクラスの実装は定義されておらず、派生クラスで独自の実装を記述する必要があります。 これらのオプションの詳細については、「抽象クラスとシール クラス、およびクラス メンバー」を参照してください。

Note

静的プロパティのアクセサーでvirtualabstract、または override 修飾子を使用すると、エラーになります。

この例では、インスタンス、静的、および読み取り専用のプロパティを示します。 キーボードから従業員の名前を受け取り、NumberOfEmployees を 1 だけインクリメントし、従業員の名前と番号を表示します。

public class Employee
{
    public static int NumberOfEmployees;
    private static int _counter;
    private string _name;

    // A read-write instance property:
    public string Name
    {
        get => _name;
        set => _name = value;
    }

    // A read-only static property:
    public static int Counter => _counter;

    // A Constructor:
    public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}

プロパティ非表示の例

この例では、派生クラスで同じ名前を持つ別のプロパティによって非表示にされている基底クラスのプロパティにアクセスする方法を示します。

public class Employee
{
    private string _name;
    public string Name
    {
        get => _name;
        set => _name = value;
    }
}

public class Manager : Employee
{
    private string _name;

    // Notice the use of the new modifier:
    public new string Name
    {
        get => _name;
        set => _name = value + ", Manager";
    }
}

class TestHiding
{
    public static void Test()
    {
        Manager m1 = new Manager();

        // Derived class property.
        m1.Name = "John";

        // Base class property.
        ((Employee)m1).Name = "Mary";

        System.Console.WriteLine("Name in the derived class is: {0}", m1.Name);
        System.Console.WriteLine("Name in the base class is: {0}", ((Employee)m1).Name);
    }
}
/* Output:
    Name in the derived class is: John, Manager
    Name in the base class is: Mary
*/

前の例で重要な点を次に示します。

  • 派生クラスのプロパティ Name により基底クラス内のプロパティ Name が非表示になっています。 このような場合、new 修飾子は派生クラスのプロパティの宣言で使用されます。
    public new string Name
    
  • キャスト (Employee) は基底クラスで非表示のプロパティにアクセスするために使用されます。
    ((Employee)m1).Name = "Mary";
    

メンバーを非表示にする詳細については、「new 修飾子」を参照してください。

プロパティ オーバーライドの例

この例では、CubeSquare の 2 つのクラスが抽象クラス Shape を実装し、その抽象 Area プロパティをオーバーライドします。 プロパティでの override 修飾子の使用に注意してください。 プログラムは、入力として辺を受け入れ、四角形と立方体の面積を計算します。 プログラムはまた、入力として面積を受け入れ、四角形と立方体の対応する辺を計算します。

abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    //constructor
    public Square(double s) => side = s;

    public override double Area
    {
        get => side * side;
        set => side = System.Math.Sqrt(value);
    }
}

class Cube : Shape
{
    public double side;

    //constructor
    public Cube(double s) => side = s;

    public override double Area
    {
        get => 6 * side * side;
        set => side = System.Math.Sqrt(value / 6);
    }
}

class TestShapes
{
    static void Main()
    {
        // Input the side:
        System.Console.Write("Enter the side: ");
        double side = double.Parse(System.Console.ReadLine());

        // Compute the areas:
        Square s = new Square(side);
        Cube c = new Cube(side);

        // Display the results:
        System.Console.WriteLine("Area of the square = {0:F2}", s.Area);
        System.Console.WriteLine("Area of the cube = {0:F2}", c.Area);
        System.Console.WriteLine();

        // Input the area:
        System.Console.Write("Enter the area: ");
        double area = double.Parse(System.Console.ReadLine());

        // Compute the sides:
        s.Area = area;
        c.Area = area;

        // Display the results:
        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}
/* Example Output:
    Enter the side: 4
    Area of the square = 16.00
    Area of the cube = 96.00

    Enter the area: 24
    Side of the square = 4.90
    Side of the cube = 2.00
*/

関連項目