プロパティ
《 初回公開:2020/07/12 , 最終更新:未 》
visual studio2019 C#8.0で動作確認。
【 目次 】
プロパティとは
プロパティ(property)は属性という意味でオブジェクトの特性・特質ををあらわす。
プログラミング言語におけるプロパティの意味は
- プロパティ (プログラミング) - Wikipedia
プロパティの読み書きは、構文上はフィールドと同様に行なえるものの、実際にはアクセサ (getter/setter) の呼び出しに変換される。
C#のプロパティは
- 【C#】プロパティを活用しよう!使う理由と実装方法を解説 | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
- プロパティ - C# によるプログラミング入門 | ++C++; // 未確認飛行 C
getアクセサーとsetアクセサー
プロパティはアクセサー(Accessor)と呼ばれたりもする。
クラスのデータにアクセスするための手段的なものという意味になるのかな。
getアクセサーとsetアクセサーはそれぞれ、ゲッター(getter),セッター(setter)と呼ばれたりもする。
典型的なプロパティのコードの例を以下に示す。
class Person { private string _name; public string Name { get { return _name; } set { _name = value; } } private int _age; public int Age { get { return _age; } set { _age = value; } } }
PersonクラスはNameプロパティとAgeプロパティをクラスのメンバーに持つ。
上記のコードで、NameプロパティやAgeプロパティの実際の値はフィールド変数_name
および_age
に格納されている。
このフィールド変数の事をバッキング フィールド(Backing Field)と呼ぶようだ。
プロパティはメソッドの変形と考える事もできる。
プロパティはメソッドでも代用できて、上記のNameプロパティをメソッドで代用すると以下のコードに相当する。
public string GetName() { return _name; } public void SetName(string value) { _name = value; }
実際、Javaでは言語仕様にプロパティはサポートされていないので、getter/setterを実装するのにこのようなコードが使われている。
getter/setterの存在意義については
読み取り専用プロパティと書き込み専用プロパティ
プロパティには、getアクセサーだけ,setアクセサーだけを記述する事もできる。
getアクセサーだけのプロパティは読み取り専用のプロパティ、setアクセサーだけのプロパティは書き込み専用のプロパティという事になる。
前述のPersonクラスのNameプロパティを読み取り専用プロパティとして記述すると
public string Name { get { return _name; } }
同様に、セッターのみでゲッターの無いプロパティを書き込み専用プロパティという。
public string Name { set { _name = value; } }
式形式のプロパティ
式本体の定義を使用してプロパティを記述する事ができる。
式本体の定義というのは
式形式のメンバーとしてNameプロパティを記述し直すと
private string _name; public string Name { get => _name; set => _name = value; }
式形式の読み取り専用プロパティは、以下のようにとりわけ簡易的な構文で記述する事ができる。
public string Name => _name;
自動実装プロパティと初期化子
NameプロパティやAgeプロパティの例でみたとおり、プロパティのコードはワンパターンな場合が多いので
もっとコードを簡略化して記述できるようにしようという事で、
以下のように記述できて、
これを自動実装プロパティ(略して自動プロパティ)と言う。
public string Name { get; set; }
機能的に見たら上記のコードは、プロパティを定義せずに直接フィールド変数を定義した、以下のコードと動作的には全く変わらない。
public string Name;
そらならば、何も自動実装プロパティなんか必要なくて、直接フィールド変数を使えばいいんじゃないかと思ってしまうが、
自動実装プロパティは一応フィールド変数をカプセル化,隠蔽していることになり、将来的にこのプロパティの内部処理を拡張したとしても外部からみた時のクラスの構造に影響がでない事に意義があるのではないだろうか。
自動実装プロパティにはプロパティの値を初期化する、初期化子の構文が用意されている。
public string Name { get; set; } = "名前の初期値";
自動実装プロパティは通常のプロパティと異なり、プロパティの値を格納するフィールド変数の定義を省略できる。
実際には、匿名のパッキングフィールドが作成されるのだが、その変数は隠されていて、このフィールド変数にアクセスする手段がない。
自動実装プロパティは読み取り専用のプロパティにする事はできるが
public string Name { get; }
書き込み専用プロパティにする事はできないようだ。
// このコードはエラーになる public string Name { set; }
なぜならプロパティに値を設定したとしても、それを利用しようとして値にアクセスしようにもその手段が無いためである。
書き込み専用プロパティも、同じように匿名のパッキングフィールドにアクセスする手段が無いため、フィールド変数に初期値を設定する手段が無くなってしまう。
しかし、これには救済手段が用意されていて。
書き込み専用の自動実装プロパティは、前述の初期化子の構文を使って初期値を設定する事ができる。
自動実装プロパティの初期化子を使って書き込み専用のプロパティに初期値を設定する
public string Name { get; set; } = "名前の初期値";
また初期化子の構文を使わない場合でも、書き込み専用プロパティにもかかわらず、コンストラクタ内部ではこの書き込み専用プロパティに値を設定する事が許されている。
class Person { public string Name { get; } public Person(string iniName) { Name = iniName; } } private static void Test9() { var person = new Person("太郎"); Console.WriteLine($"person.Name = {person.Name}"); }
実行結果
person.Name = 太郎
また、初期値を設定してその値を読み込むだけであれは、読み取り専用プロパティの自動実装プロパティを使わずとも、前述の式形式の読み取り専用プロパティを使った方が若干コードが短くなる。
しかし、この場合は自動実装プロパティと異なりコンストラクタで値を設定する事はできない。
public string Name => "名前の初期値";
インターフェースと自動実装プロパティ
インターフェースメンバーのデフォルトの実装として、自動実装プロパティを宣言することはできない。
なぜならばインターフェースに「状態を持つ」事になるフィールドメンバーは許されないため。
インターフェースに自動実装プロパティと同じコードを記述した場合、それは自動実装プロパティのデフォルトの実装ではなく、abstractなプロパティであってインターフェースを実装するクラスでプロパティを再実装する必要がある。
interface IPerson { // Nameプロパティは既定のプロパティでは無くAbstractなプロパティ public string Name { get; set; } } class PersonI : IPerson { // IPersonインターフェースを実装したクラスはNameプロパティを実装する必要がある。 public string Name { get; set; } }
ゲッターとセッターで異なるアクセスレベルを指定する
ゲッターとセッターで異なるアクセスレベルを指定したい場合がある。
例えばクラスの外部から値を読み出す事を許すが、値の設定はクラス内部からしか変更させたくない。
このような場合、プロパティのアクセスレベルをより広い方のアクセスレベルに指定しておいて、狭くしたい方のアクセスレベルをゲッターかセッターに対して個別に設定することになる。
読み取り側のプロパティはpublic , 書き込み側のプロパティはprivate
public string Name { get { return _name; } private set { _name = value; } }
式形式のプロパティや自動実装プロパティも同様に、ゲッターとセッターで異なるアクセスレベルを指定する事ができる。
式形式のプロパティに異なるアクセスレベルを指定
public string Name { get => _name; private set => _name = value; }
自動実装プロパティに異なるアクセスレベルを指定
public string Name { get; private set; }
ゲッターとセッターの両方に、個別のアクセスレベルを指定する事はエラーとなる。
以下のコードはエラーになる
// エラー CS0274 アクセシビリティ修飾子は、プロパティまたはインデクサー 'Person.Name' の両方のアクセサーに指定できません。 public string Name { private get { return _name; } internal set { _name = value; } }
また、プロパティのアクセスレベルを狭くしておいて、個別のゲッターかセッターに対するアクセスレベルを広げる事もエラーになる。
以下のコードはエラーになる
// エラー CS0273 Person.Name.get' アクセサーのアクセシビリティ修飾子は、プロパティまたはインデクサー 'Person.Name' よりも制限されていなければなりません。 private string Name { public get { return _name; } set { _name = value; } }
プロパティの継承
プロパティもメソッドの一種と考えられる。
であればabstractやvirtual,override、newなどのキーワードが使えて。
試してみると
abstract class PersonBaseA { abstract public string Name { get; set; } } // abstractなプロパテをメンバーをオーバーライドするクラス class PersonA : PersonBaseA { private string _name; public override string Name { get => _name; set => _name=value; } } // virtualなプロパテをメンバーに持つクラス class PersonBaseV { virtual public string Name { get; set; } = "一郎"; } // virtualなプロパテをメンバーをオーバーライドするクラス class PersonV : PersonBaseV { override public string Name { get => $"{base.Name}さん" ; } } // newキーワードにより継承されたプロパティを隠ぺいするクラス class PersonVN : PersonBaseV { private string _name; public new string Name { get => $"Mr.{_name}"; set => _name = value; } } public static void Test1() { // abstractからオーバーライドしたプロパティを呼び出す PersonBaseA personA = new PersonA() { Name="太郎"}; Console.WriteLine($"personA.Name = {personA.Name}"); // virtualからオーバーライドしたプロパティを呼び出す PersonBaseV personV = new PersonV() { Name = "次郎" }; Console.WriteLine($"personV.Name = {personV.Name}"); // newキーワードのプロパティを呼び出す var personVN = new PersonVN() { Name = "三郎" }; Console.WriteLine($"personVN.Name = {personVN.Name}"); // newキーワードにより隠ぺいされたプロパティを呼び出す Console.WriteLine($"((PersonBaseV)personVN).Name = {((PersonBaseV)personVN).Name}"); }
結果は予測できるでしょうか。
実行結果
personA.Name = 太郎 personV.Name = 次郎さん personVN.Name = Mr.三郎 ((PersonBaseV)personVN).Name = 一郎