プロパティの使用 (C# プログラミング ガイド)
プロパティは、フィールドとメソッドの両方の側面を兼ね備えています。オブジェクトを使用する側から見ると、プロパティはフィールドのように見えます。プロパティへのアクセス方法はフィールドと同じです。クラスを実装する側から見ると、プロパティは、get アクセサーと set アクセサーのいずれか、または両方を表すコード ブロックです。get アクセサーのコード ブロックは、プロパティが読み込まれたときに実行されます。set アクセサーのコード ブロックは、プロパティに新しい値が割り当てられたときに実行されます。set アクセサーなしのプロパティは、読み取り専用と見なされます。get アクセサーなしのプロパティは、書き込み専用と見なされます。両方のアクセサーを持つプロパティは、読み書きが可能です。
フィールドとは異なり、プロパティは変数には分類されません。したがって、プロパティを ref (C# リファレンス) パラメーターまたは out (C# リファレンス) パラメーターとして渡すことはできません。
プロパティの用途はさまざまです。たとえば、変更を許可する前にデータを検証できます。データベースなど、他のソースからクラス上のデータが取得されている場合、そのデータを透過的に公開できます。データが変更されたときに、イベントを発行したり他のフィールド値を変更したりするなど、アクションを実行できます。
クラス ブロック内でプロパティを宣言するには、フィールドのアクセス レベル、プロパティの種類、プロパティの名前の順に宣言し、その後に get アクセサーと set アクセサーのいずれかまたは両方を宣言するコード ブロックが続きます。次に例を示します。
public class Date
{
private int month = 7; // Backing store
public int Month
{
get
{
return month;
}
set
{
if ((value > 0) && (value < 13))
{
month = value;
}
}
}
}
この例では、Month がプロパティとして宣言されています。これは、set アクセサーで Month の値が 1 ~ 12 の間に設定されるようにするためです。Month プロパティでは、プライベート フィールドを使用して実際の値を追跡します。プロパティ データの実際の位置は、プロパティの "バッキング ストア" と呼ばれます。プロパティでは、プライベート フィールドをバッキング ストアとして使用することは一般的です。プロパティの呼び出しによってのみフィールドを変更できるように、フィールドはプライベートとマークされます。パブリック アクセスとプライベート アクセスの制約の詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。
自動実装するプロパティは、単純なプロパティ宣言に簡略化した構文を提供します。詳細については、「自動実装するプロパティ (C# プログラミング ガイド)」を参照してください。
get アクセサー
get アクセサーの本体は、メソッドの本体と似ています。アクセサーは、プロパティの型の値を返す必要があります。get アクセサーを実行することは、フィールドの値を読み取ることに相当します。たとえば、get アクセサーからプライベート値が戻ったときに最適化が有効な場合、get アクセサー メソッドの呼び出しは、コンパイルでインライン展開されます。そのため、メソッド呼び出しのオーバーヘッドはありません。ただし、仮想 get アクセサー メソッドは、インライン展開できません。これは、コンパイル時点では、実行時に実際に呼び出されるメソッドをコンパイラで判断できないためです。次に示すのは、プライベート フィールド name の値を返す get アクセサーです。
class Person
{
private string name; // the name field
public string Name // the Name property
{
get
{
return name;
}
}
}
代入の対象になる場合を除き、プロパティを参照すると、プロパティの値を読み取るために get アクセサーが呼び出されます。次に例を示します。
Person person = new Person();
//...
System.Console.Write(person.Name); // the get accessor is invoked here
get アクセサーは return ステートメントまたは throw ステートメントで終了する必要があり、さらに、制御がアクセサーの本体から離れないようにします。
get アクセサーを使ってオブジェクトの状態を変更するのは、不適切なプログラミング スタイルです。たとえば、次のアクセサーには、number フィールドにアクセスするたびにオブジェクトの状態が変化するという副作用があります。
private int number;
public int Number
{
get
{
return number++; // Don't do this
}
}
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; // the name field
public string Name // the Name property
{
get
{
return name;
}
set
{
name = value;
}
}
}
プロパティに値を代入すると、新しい値を渡す引数を指定して set アクセサーが呼び出されます。次に例を示します。
Person person = new Person();
person.Name = "Joe"; // the set accessor is invoked here
System.Console.Write(person.Name); // the get accessor is invoked here
set アクセサーでローカル変数の宣言に暗黙のパラメーター名 (value) を使用するとエラーになります。
解説
プロパティは、public、private、protected、internal、または protected internal とマークできます。これらのアクセス修飾子により、クラスのユーザーがプロパティにどのようにアクセスできるかが定義されます。同じプロパティでも、get アクセサーと set アクセサーとでアクセス修飾子が異なることがあります。たとえば、get は、その型の外部からは読み取り専用アクセスのみを許可する public で、set は private または protected のことがあります。詳細については、「アクセス修飾子 (C# プログラミング ガイド)」を参照してください。
プロパティは、static キーワードを使用して静的プロパティと宣言することもできます。このように宣言すると、クラスのインスタンスが存在しない場合でも、呼び出し元がいつでもプロパティにアクセスできます。詳細については、「静的クラスと静的クラス メンバー (C# プログラミング ガイド)」を参照してください。
プロパティは、virtual キーワードを使用して仮想プロパティとマークすることもできます。この場合、派生クラスでは、override キーワードを使用して、プロパティの動作をオーバーライドできます。これらのオプションの詳細については、「継承 (C# プログラミング ガイド)」を参照してください。
仮想プロパティをオーバーライドするプロパティは、sealed にすることもできます。この場合、派生クラスでは、プロパティが仮想でなくなります。最後に、プロパティは abstract と宣言できます。この場合、クラスには実装が存在しないので、派生クラスで独自の実装を記述する必要があります。これらのオプションの詳細については、「抽象クラスとシール クラス、およびクラス メンバー (C# プログラミング ガイド)」を参照してください。
[!メモ]
静的プロパティのアクセサーで virtual (C# リファレンス)、abstract (C# リファレンス)、または override (C# リファレンス) のいずれかの修飾子を使うと、エラーになります。
使用例
次の例では、インスタンス プロパティ、静的プロパティ、および読み取り専用プロパティが使われています。キーボードから入力された従業員の名前を受け取り、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 { return name; }
set { name = value; }
}
// A read-only static property:
public static int Counter
{
get { return counter; }
}
// A Constructor:
public Employee()
{
// Calculate the employee's number:
counter = ++counter + NumberOfEmployees;
}
}
class TestEmployee
{
static void Main()
{
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee();
e1.Name = "Claude Vige";
System.Console.WriteLine("Employee number: {0}", Employee.Counter);
System.Console.WriteLine("Employee name: {0}", e1.Name);
}
}
/* Output:
Employee number: 108
Employee name: Claude Vige
*/
基本クラスのプロパティは、派生クラスにある同じ名前の別のプロパティによって隠ぺいされています。次の例は、この基本クラスのプロパティにアクセスする方法を示しています。
public class Employee
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
public class Manager : Employee
{
private string name;
// Notice the use of the new modifier:
public new string Name
{
get { return name; }
set { name = value + ", Manager"; }
}
}
class TestHiding
{
static void Main()
{
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 修飾子 (C# リファレンス)」を参照してください。
次の例では、2 つのクラス Cube と Square が抽象クラス 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()
{
// 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
*/
参照
関連項目
インターフェイスのプロパティ (C# プログラミング ガイド)