Top > .NET Visual C#言語入門 > プロパティ
プログラム構造 列挙型(enum) コンストラクタ・デストラクタ
スケルトン 配列 メソッド
クラス内の記述構成 構造体 プロパティ
HalloWorld 修飾子 静的クラスと静的メンバ
コマンドライン引数 プリプロセッサ ディレクティブ フィールド
コメント 例外と例外処理 演算子のオーバーロード
ステートメント(文) 可変個引数リスト(params) イベント(割り込み)
式(演算子) 名前空間 デリゲート
変数とデータ型 クラスとオブジェクト インデクサ
型変換(キャスト) クラス(class) デリゲート&イベント発生と受信サンプル
定数(Const/Readonly) 部分クラス定義  
文字列(string) 継承  

プロパティ

プロパティは、プライベート フィールドの値の読み取り、書き込み、または計算を行う、柔軟な機構が用意されたメンバです。プロパティは、パブリック データのメンバと同様に使用できますが、実際はアクセサという特殊なメソッドが使用されます。メソッドの安全性と柔軟性を維持しながら、簡単にデータへアクセスできます。

この例では、TimePeriod クラスに期間が格納されています。内部的に、クラスに秒単位で期間が格納されていますが、クライアントが時間単位で期間を指定できる Hours というプロパティも用意されています。Hours プロパティのアクセサでは、時間と秒の変換が実行されます。

使用例

class TimePeriod
{
    private double seconds;
    public double Hours
    {
        get { return seconds / 3600; }
        set { seconds = value * 3600; }
    }
}

class Program
{
    static void Main()
    {
        TimePeriod t = new TimePeriod();

        t.Hours = 24;
        System.Console.WriteLine("Time in hours: " + t.Hours);
    }
}

出力

Time in hours: 24

プロパティの概要

  • プロパティによって、クラスの実装や検査のコードを隠ぺいしたままで、値の取得と設定を行うことができるようになります。
  • get プロパティ アクセサはプロパティ値を返すときに使用され、set アクセサは新しい値を割り当てるときに使用されます。2 つのアクセサには異なるアクセス レベルを指定できます。詳細については、「アクセサのアクセシビリティ」を参照してください。
  • value キーワードは、set インデクサで割り当てられている値を定義するときに使用されます。
  • set メソッドが実装されないプロパティは、読み取り専用です。

プロパティの使用

プロパティは、フィールドとメソッドの両方の側面を兼ね備えています。オブジェクトを使用する側から見ると、プロパティはフィールドのように見えます。プロパティへのアクセス方法はフィールドとまったく同じです。クラスを実装する側から見ると、プロパティは、get アクセサと set アクセサのいずれか、または両方を表すコード ブロックです。get アクセサのコード ブロックは、プロパティが読み込まれたときに実行されます。set アクセサのコード ブロックは、プロパティに新しい値が割り当てられたときに実行されます。set アクセサなしのプロパティは、読み取り専用と見なされます。get アクセサなしのプロパティは、書き込み専用と見なされます。両方のアクセサを備えるプロパティは、読み書きが可能です。

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

プロパティの用途はさまざまです。たとえば、変更を許可する前にデータを検証できます。データベースなど、他のソースからクラス上のデータが取得されている場合、そのデータを透過的に公開できます。データが変更されたときに、イベントを発行したり他のフィールド値を変更したりするなど、アクションを実行できます。

クラス ブロック内でプロパティを宣言するには、フィールドのアクセス レベル、プロパティの種類、プロパティの名前の順に宣言し、その後に get アクセサと set アクセサのいずれかまたは両方を宣言します。たとえば、次のようにします。

public class Date
{
    private int month = 7;
    public int Month
    {
        get { return month; }
        set
        {
            if ((value > 0) && (value < 13))
            {
                month = value;
            }
        }
    }
}

この例では、Month がプロパティとして宣言されています。これは、set アクセサで Month の値が 1 ~ 12 の間に設定されるようにするためです。Month プロパティでは、プライベート フィールドを使用して実際の値を追跡します。プロパティ データの実際の位置は、プロパティの "バッキング ストア" と呼ばれます。プロパティでは、プライベート フィールドをバッキング ストアとして使用することは一般的です。プロパティの呼び出しによってのみフィールドを変更できるように、フィールドはプライベートとマークされます。

get アクセサ

 get アクセサの本体は、メソッドの本体と似ています。アクセサは、プロパティの型の値を返す必要があります。get アクセサを実行することは、フィールドの値を読み取ることに相当します。たとえば、get アクセサからプライベート値が戻ったときに最適化が有効な場合、get アクセサ メソッドの呼び出しは、コンパイルでインライン展開されます。そのため、メソッド呼び出しのオーバーヘッドはありません。ただし、仮想 get アクセサ メソッドは、インライン展開できません。これは、コンパイル時点では、実行時に実際に呼び出されたメソッドをコンパイラで判断できないためです。次に示すのは、プライベート フィールド name の値を返す get アクセサです。
class Person
{
    private string name;

    public string Name
    {
        get { return name; }
    }
}

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

Person p1 = new Person();
//...

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

 get アクセサは return ステートメントまたは throw ステートメントで終了する必要があり、さらに、制御がアクセサの本体から離れないようにします。

 get アクセサを使ってオブジェクトの状態を変更するのは、不適切なプログラミング スタイルです。たとえば、次のアクセサには、number フィールドにアクセスするたびにオブジェクトの状態が変化するという副作用があります。

private int number;

public int Number
{
    get { return number++; }
}

get アクセサを使えば、フィールドの値を返したり、フィールドの値を計算して返したりできます。次に例を示します。

class Employee
{
    private string name;

    public string Name
    {
        get { return name != null ? name : "NA"; }
    }
}

前のコード例では、Name プロパティに値を代入しないと、NA という値を返します。

set アクセサ

 set アクセサは、戻り値が void のメソッドと似ています。プロパティの型の value という名前の暗黙のパラメータを使用します。次の例では、set アクセサを Name プロパティに追加しています。
class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

プロパティに値を代入すると、新しい値を渡す引数を指定して set アクセサが呼び出されます。次に例を示します。

Person p1 = new Person();
p1.Name = "Joe";
System.Console.Write(p1.Name);

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

解説

プロパティは、publicprivateprotectedinternal、または protected internal とマークできます。これらのアクセス修飾子により、クラスのユーザーがプロパティにどのようにアクセスできるかが定義されます。同じプロパティでも、get アクセサと set アクセサとでアクセス修飾子が異なることがあります。たとえば、get は、その型の外部からは読み取り専用アクセスのみを許可する public で、setprivate または protected のことがあります。詳細については、「アクセス修飾子」を参照してください。

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

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

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

メモ
静的プロパティのアクセサで virtual、abstract、overrideのいずれかの修飾子を使うと、エラーになります。

例 1

 次の例では、インスタンス プロパティ、静的プロパティ、および読み取り専用プロパティが使われています。キーボードから入力された従業員の名前を受け取り、NumberOfEmployees の値を 1 だけインクリメントし、従業員の名前と番号を表示します。
public class Employee
{
    public static int NumberOfEmployees;
    private static int counter;
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public static int Counter
    {
        get { return counter; }
    }

    public Employee()
    {
        counter = ++counter + NumberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        Employee.NumberOfEmployees = 100;
        Employee e1 = new Employee();
        e1.Name = "Claude Vige";

        System.Console.WriteLine("Employee number: {0}", Employee.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}

出力 1

Employee number: 101
Employee name: Claude Vige

例 2

 基本クラスのプロパティは、派生クラスにある同じ名前の別のプロパティによって隠ぺいされています。次の例は、この基本クラスのプロパティにアクセスする方法を示しています。
public class Employee
{
    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

public class Manager : Employee
{
    private string name;

    public new string Name
    {
        get { return name; }
        set { name = value + ", Manager"; }
    }
}

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

        m1.Name = "John";
        ((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);
    }
}

出力 2

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 修飾子 (C# リファレンス)」を参照してください。

例 3

 次の例では、2 つのクラス CubeSquare が抽象クラス Shape を実装しており、その抽象プロパティ Area をオーバーライドしています。プロパティに対する override 修飾子の使用に注意してください。プログラムは、入力として 1 辺の長さ (side) を受け取り、正方形の面積 (square) と立方体の表面積 (cube) を計算します。また、入力として面積を受け取り、正方形の 1 辺の長さと立方体の 1 辺の長さを計算することもできます。
abstract class Shape
{
    public abstract double Area
    {
        get;
        set;
    }
}

class Square : Shape
{
    public double side;

    public Square(double s)  //constructor
    {
        side = s;
    }

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

class Cube : Shape
{
    public double side;

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

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

        Square s = new Square(side);
        Cube c = new Cube(side);

        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();

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

        s.Area = area;
        c.Area = area;

        System.Console.WriteLine("Side of the square = {0:F2}", s.side);
        System.Console.WriteLine("Side of the cube = {0:F2}", c.side);
    }
}

入力

4
24

出力 3

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

インターフェイスのプロパティ

インターフェイスに対してプロパティを宣言できます。次に示すのは、インターフェイス インデクサのアクセサの例です。

public interface ISampleInterface
{
    string Name
    {
        get;
        set;
    }
}

インターフェイス プロパティのアクセサには、本体がありません。したがって、アクセサの目的は、プロパティが読み取り/書き込み、読み取り専用、または書き込み専用のいずれであるかを示すことです。

使用例

 次の例では、インターフェイス IEmployee には、読み取り/書き込みプロパティ Name と読み取り専用プロパティ Counter があります。Employee クラスは IEmployee インターフェイスを実装し、この 2 つのプロパティを使用します。プログラムは、新しい従業員の名前と現在の従業員数を読み取り、従業員の名前と計算した従業員番号を表示します。

プロパティの完全修飾名を使用して、メンバが宣言されているインターフェイスを参照しています。次に例を示します。

string IEmployee.Name
{
    get { return "Employee Name"; }
    set { }
}

これは、「明示的なインターフェイスの実装 (C# プログラミング ガイド)」と呼ばれます。たとえば、Employee クラスが 2 つのインターフェイス ICitizenIEmployee を実装し、両方のインターフェイスに Name プロパティがある場合は、明示的なインターフェイス メンバの実装が必要になります。つまり、次のプロパティ宣言では IEmployee インターフェイスの Name プロパティが実装されます。

string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}

一方、次の宣言では ICitizen インターフェイス Name プロパティが実装されます。

string ICitizen.Name
{
    get { return "Citizen Name"; }
    set { }
}
interface IEmployee
{
    string Name
    {
        get;
        set;
    }

    int Counter
    {
        get;
    }
}

public class Employee : IEmployee
{
    public static int numberOfEmployees;

    private string name;
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    private int counter;
    public int Counter
    {
        get { return counter; }
    }

    public Employee()
    {
        counter = ++counter + numberOfEmployees;
    }
}

class TestEmployee
{
    static void Main()
    {
        System.Console.Write("Enter number of employees: ");
        Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());

        Employee e1 = new Employee();
        System.Console.Write("Enter the name of the new employee: ");
        e1.Name = System.Console.ReadLine();

        System.Console.WriteLine("The employee information:");
        System.Console.WriteLine("Employee number: {0}", e1.Counter);
        System.Console.WriteLine("Employee name: {0}", e1.Name);
    }
}

入力

210
Hazem Abolrous

出力例

Enter number of employees: 210
Enter the name of the new employee: Hazem Abolrous
The employee information:
Employee number: 211
Employee name: Hazem Abolrous

プロパティとインデクサの比較

インデクサはプロパティと似ています。次の表で示す相違点を除けば、プロパティのアクセサに対して定義されているすべての規則が、インデクサのアクセサにも同じように適用されます。

プロパティ インデクサ
メソッドをパブリック データ メンバのように呼び出すことができます。 オブジェクトのメソッドを、オブジェクトが配列のように呼び出すことができます。
簡易名でアクセスされます。 インデックスでアクセスされます。
静的メンバまたはインスタンス メンバになることができます。 インスタンス メンバである必要があります。
プロパティの get アクセサにはパラメータがありません。 インデクサの get アクセサには、インデクサと同じ仮パラメータ リストがあります。
プロパティの set アクセサには、暗黙の value パラメータがあります。 インデクサの set アクセサには、value パラメータの他に、インデクサと同じ仮パラメータ リストがあります

非対称アクセサのアクセシビリティ

プロパティやインデクサの get および set の部分は、アクセサと呼ばれます。既定では、これらのアクセサの参照範囲、つまりアクセス レベルは、それが属するプロパティまたはインデクサのアクセス レベルと同じになります。詳細については、「アクセシビリティ レベル」を参照してください。ただし、これらのアクセサのいずれかへのアクセスを制限すると便利な場合があります。この場合は、通常、set アクセサのアクセシビリティを制限し、get アクセサはパブリックにアクセス可能にしておきます。次に例を示します。

public string Name
{
    get { return name; }
    protected set { name = value; }
}

この例では、Name というプロパティが get アクセサおよび set アクセサを定義しています。get アクセサは、プロパティ自体のアクセシビリティ レベル (この場合は public) を受け取りますが、set アクセサは、protected アクセス修飾子をアクセサ自体に適用することにより、明示的に制限されています。

アクセス修飾子によるアクセサの制限

プロパティやインデクサでアクセス修飾子を使用する際には、以下の条件が適用されます。

  • アクセス修飾子は、インターフェイスや明示的なインターフェイス メンバ実装で使用できません。
  • アクセス修飾子を使用できるのは、プロパティやインデクサが setget の両方のアクセサを備えている場合に限られます。この場合、修飾子は、これら 2 つのアクセスのうちの一方でのみ許可されます。
  • プロパティやインデクサにオーバーライド修飾子がある場合、アクセス修飾子は、オーバーライドされたアクセサのアクセサに一致する必要があります。
  • アクセサのアクセシビリティ レベルは、プロパティやインデクサ自体のアクセシビリティ レベルよりも制限する必要があります。

アクセサのオーバーライド時のアクセス修飾子

 プロパティやインデクサをオーバーライドした場合、オーバーライドされたアクセサは、オーバーライド側のコードにアクセスできる必要があります。また、プロパティとインデクサの両方のアクセシビリティ レベル、およびアクセサのアクセシビリティ レベルは、オーバーライドされた、対応するプロパティとインデクサ、およびアクセサに適合する必要があります。次に例を示します。
public class Parent
{
    public virtual int TestProperty
    {
        protected set { }
        get { return 0; }
    }
}
public class Kid : Parent
{
    public override int TestProperty
    {
        protected set { }
        get { return 0; }
    }
}
 

インターフェイスの実装

アクセサを使用してインターフェイスを実装するときは、アクセサでアクセス修飾子を使用できません。ただし、get などの 1 つのアクセサを使用してインターフェイスを実装する場合は、次の例に示すように、もう 1 つのアクセサでアクセス修飾子を使用できます。

public interface ISomeInterface
{
    int TestProperty
    {
        get;   
    }
}

public class TestClass : ISomeInterface
{
    public int TestProperty
    {
        get { return 10; }
        protected set { }
    }
}

アクセサのアクセシビリティ ドメイン

アクセサでアクセス修飾子を使用した場合は、この修飾子によって、アクセサのアクセシビリティ ドメインが決定されます。

アクセサでアクセス修飾子を使用しない場合は、プロパティやインデクサのアクセシビリティ レベルによって、アクセサのアクセシビリティ ドメインが決定されます。

使用例

次の例には、BaseClassDerivedClassMainClass の 3 つのクラスがあります。BaseClass には NameId の 2 つのプロパティがあり、これらのプロパティは派生クラスにもあります。この例は、protected や private などの制限付きのアクセス修飾子を使用したときに、DerivedClassId プロパティを BaseClassId プロパティによってどのように隠ぺいできるかを示しています。そのため、前者のプロパティに値を割り当てると、代わりに BaseClass クラスのプロパティが呼び出されます。アクセス修飾子を public に置き換えると、このプロパティにアクセスできるようになります。

また、次の例は、privateprotected などの制限付きのアクセス修飾子を、DerivedClassName プロパティの set アクセサに指定すると、このアクセサにアクセスできなくなり、このプロパティに値を割り当てると、エラーが生成されることも示しています。

public class BaseClass
{
    private string name = "Name-BaseClass";
    private string id = "ID-BaseClass";

    public string Name
    {
        get { return name; }
        set { }
    }

    public string Id
    {
        get { return id; }
        set { }
    }
}

public class DerivedClass : BaseClass
{
    private string name = "Name-DerivedClass";
    private string id = "ID-DerivedClass";

    new public string Name
    {
        get { return name; }
        set { name = value; }
    }

    new private string Id
    {
        get { return id; }
        set { id = value; }
    }
}

class MainClass
{
    static void Main()
    {
        BaseClass b1 = new BaseClass();
        DerivedClass d1 = new DerivedClass();

        b1.Name = "Mary";
        d1.Name = "John";
        
        b1.Id = "Mary123";
        d1.Id = "John123";.

        System.Console.WriteLine("Base: {0}, {1}", b1.Name, b1.Id);
        System.Console.WriteLine("Derived: {0}, {1}", d1.Name, d1.Id);
    }
}

出力

Name and ID in the base class: Name-BaseClass, ID-BaseClass
Name and ID in the derived class: John, ID-BaseClass

説明

宣言 new private string Idnew public string Id に置き換えると、次の出力が得られます。

Name and ID in the base class: Name-BaseClass, ID-BaseClass
Name and ID in the derived class: John, John123

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします