.NET Visual C#言語入門 > プロパティ
のぶ亭『プログラミングの相談窓口』 … 様々なプログラミング問題を個別対応致します |
プロパティ
プロパティは、プライベート フィールドの値の読み取り、書き込み、または計算を行う、柔軟な機構が用意されたメンバです。プロパティは、パブリック データのメンバと同様に使用できますが、実際はアクセサという特殊なメソッドが使用されます。メソッドの安全性と柔軟性を維持しながら、簡単にデータへアクセスできます。
この例では、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); } }
出力
プロパティの概要
- プロパティによって、クラスの実装や検査のコードを隠ぺいしたままで、値の取得と設定を行うことができるようになります。
- 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 アクセサ
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 アクセサ
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) を使用するとエラーになります。
解説
プロパティは、public、private、protected、internal、または protected internal とマークできます。これらのアクセス修飾子により、クラスのユーザーがプロパティにどのようにアクセスできるかが定義されます。同じプロパティでも、get アクセサと set アクセサとでアクセス修飾子が異なることがあります。たとえば、get は、その型の外部からは読み取り専用アクセスのみを許可する public で、set は private または protected のことがあります。詳細については、「アクセス修飾子」を参照してください。
プロパティは、static キーワードを使用して静的プロパティと宣言することもできます。このように宣言すると、クラスのインスタンスが存在しない場合でも、呼び出し元がいつでもプロパティにアクセスできます。詳細については、「静的クラスと静的クラス メンバ」を参照してください。
プロパティは、virtual キーワードを使用して仮想プロパティとマークすることもできます。この場合、派生クラスでは、override キーワードを使用して、プロパティの動作をオーバーライドできます。これらのオプションの詳細については、「継承 」を参照してください。
仮想プロパティをオーバーライドするプロパティは、sealed にすることもできます。この場合、派生クラスでは、プロパティが仮想でなくなります。最後に、プロパティは abstract と宣言できます。この場合、クラスには実装が存在しないので、派生クラスで独自の実装を記述する必要があります。これらのオプションの詳細については、「抽象クラスとシール クラス、およびクラス メンバ」を参照してください。
メモ |
---|
静的プロパティのアクセサで virtual、abstract、overrideのいずれかの修飾子を使うと、エラーになります。 |
例 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 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 base class is: Mary
コードの説明
- 派生クラスのプロパティ Name は、基本クラスのプロパティ Name
を隠ぺいしています。この場合、派生クラスのプロパティの宣言では new
修飾子が使われます。
public new string Name
- 基本クラスの隠ぺいされたプロパティにアクセスするには、キャスト (Employee)
を使用します。
((Employee)m1).Name = "Mary";
メンバを隠ぺいする方法の詳細については、「new 修飾子 (C# リファレンス)」を参照してください。
例 3
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
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; } }
インターフェイス プロパティのアクセサには、本体がありません。したがって、アクセサの目的は、プロパティが読み取り/書き込み、読み取り専用、または書き込み専用のいずれであるかを示すことです。
使用例
プロパティの完全修飾名を使用して、メンバが宣言されているインターフェイスを参照しています。次に例を示します。
string IEmployee.Name { get { return "Employee Name"; } set { } }
これは、「明示的なインターフェイスの実装 (C# プログラミング ガイド)」と呼ばれます。たとえば、Employee クラスが 2 つのインターフェイス ICitizen と IEmployee を実装し、両方のインターフェイスに 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 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 アクセス修飾子をアクセサ自体に適用することにより、明示的に制限されています。
アクセス修飾子によるアクセサの制限
プロパティやインデクサでアクセス修飾子を使用する際には、以下の条件が適用されます。
- アクセス修飾子は、インターフェイスや明示的なインターフェイス メンバ実装で使用できません。
- アクセス修飾子を使用できるのは、プロパティやインデクサが set と get の両方のアクセサを備えている場合に限られます。この場合、修飾子は、これら 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 { } } }
アクセサのアクセシビリティ ドメイン
アクセサでアクセス修飾子を使用した場合は、この修飾子によって、アクセサのアクセシビリティ ドメインが決定されます。
アクセサでアクセス修飾子を使用しない場合は、プロパティやインデクサのアクセシビリティ レベルによって、アクセサのアクセシビリティ ドメインが決定されます。
使用例
次の例には、BaseClass、DerivedClass、MainClass の 3 つのクラスがあります。BaseClass には Name と Id の 2 つのプロパティがあり、これらのプロパティは派生クラスにもあります。この例は、protected や private などの制限付きのアクセス修飾子を使用したときに、DerivedClass の Id プロパティを BaseClass の Id プロパティによってどのように隠ぺいできるかを示しています。そのため、前者のプロパティに値を割り当てると、代わりに BaseClass クラスのプロパティが呼び出されます。アクセス修飾子を public に置き換えると、このプロパティにアクセスできるようになります。
また、次の例は、private や protected などの制限付きのアクセス修飾子を、DerivedClass の Name プロパティの 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 derived class: John, ID-BaseClass
説明
Name and ID in
the base class: Name-BaseClass, ID-BaseClass
Name and ID in the derived class: John, John123