C#のgetterとsetterについて
前知識(プロパティとフィールドとアクセサ)
public class DefaultProp
{
// フィールド
private string name;
// バッキングフィールド
private int id;
// プロパティ
public int ID
{
// getアクセサ
get { return id; }
// setアクセサ
// valueはプロパティに代入された値
set { id = value; }
}
// プロパティは内部的には以下のようになる。
// public 戻り値 プロパティ名
// {
// ....
// }
}
フィールド
クラス内に宣言される変数のことです。public/protectedであればインスタンスから直接呼び出すことが可能です。基本的には非公開にしてプロパティから操作します。プロパティから操作を行うフィールドを「バッキングストア」又は「バッキングフィールド」と言います。バッキングフィールドへは、外部からアクセスすることはできません。
プロパティ
フィールドに格納されている値(データ)を、読み取ったり書き込んだりするために設定するための手段を指します。プロパティは、クラス外部から見た時は、メンバー変数のように振る舞い、クラス内部から見た時はメソッドのように振る舞います。
アクセサ
プロパティを構成するgetter/setterをアクセサと呼びます。一般的には、それぞれgetアクセサ(読み取り時実行)、setアクセサ(書き込み時実行)と呼ばれています。
なぜgetter/setterのようなプロパティが必要となるのか?
まず第一に、オブジェクト指向言語では、"public"な変数はカプセル化を破壊するため、使用が推奨されていません。ここでプロパティを使用することにより、フィールド自体の独立性が高くなって、仕様変更に強くなります。
では、実際どのような場面で必要になるのか、以下を参考にしてみて下さい。
- メンバ変数を読み取り(又は書き込み)専用にすることができる。
- setterに値の正当性チェック処理を入れ、範囲内の値のみ代入を行うようにできる。
- 拡張性を持たせるために、固有の処理を入れることができる。
また、VisualStudioのようなIDEを使っている場合には、コードレンズなどのツールサポートの恩恵を受けやすいです。
デメリットは?
- 見た目上は変数なので、管理が難しくなりやすい。
- 純粋なgetter/setterを使用するとカプセル化は実現されない。
- IDEで追いづらい。
プロパティの基本構文
プロパティはgetアクセサとsetアクセサの2つで構成されています。この2つはデフォルトで"public"扱いとなっています。ここで、外部から見た時に読み取り(又は書き込み)専用とするためには、どちらか一方のアクセサが持つアクセス修飾子を"private"か"protected"にする必要があります。アクセス修飾子を変更できるのは、どちらか一方、という部分に注意して下さい。
public class ReadOnlyProp
{
// バッキングフィールド
private int id;
// プロパティ
public int ID
{
// getアクセサ
// public とは書いていないけど public である。
get { return id; }
// setアクセサ
// valueはプロパティに代入された値
private set { id = value; }
}
}
ここで、プロパティを使用する際には以下の事柄に注意して下さい。
- getアクセサ内部で、バッキングフィールドのデータを変更する処理は推奨されていません。
- setアクセサ内部で、ローカル変数の宣言に"value"という名前を使用しないで下さい。
自動プロパティ(auto-property)機能
この自動プロパティ機能は「C#3.0」から実装されました。どういったものか早速以下のコードを眺めて見て下さい。
public class AutoProp
{
//// C#3.0以降の自動プロパティ(auto-property)機能
// プロパティ
public int ID { get; private set; }
public AutoProp()
{
ID = 100;
}
}
この段階で、アクセサの実装部分を省略して書けるようになりました。ただし、フィールドの初期化を行うためには結局元来のような書き方をしたり、コンストラクターに初期化設定処理を書く必要があったのです。これらのデメリットは後述するC#6.0から解消されました。
自動プロパティ改+初期値設定機能
C#6.0で、C#3.0で問題になっていた点が見事克服されました。以下のコードをみて下さい。
public class AutoProp
{
//// C#6.0以降の自動プロパティ(auto-property)+初期値設定機能
public int ID { get; } = 100;
// 内部ではデータを変更したい場合、以下のように書く。
// public int ID { get; private set; } = 100;
}
なんとプロパティに直接代入をするような形で初期化が行われています。しかも"private set;"が消えています!外部からも内部からも読み取り専用である時、「setアクセサは"private"である」ことを前提に省略することができます。この省略機能は、setアクセサにのみ適用可能という部分に注意して下さい。getアクセサのみとなったプロパティは、コンパイラによって"readonlyフィールド"が生成されるので、readonlyと同じ扱いとなります。
もし内部からは変更を行える状態にしたい場合は、省略機能を使用せずに、"private set;"を記述してプロパティを作成して下さい。
書き込み専用のプロパティを作成したい場合は、以下のようなプロパティを作成して下さい。
public class WriteOnlyProp
{
public int WriteOnlyAutoProp { private get; set; } = 100;
////又は////
private int hoge;
public int WriteOnlyProp
{
private get { return hoge;}
set { hoge = value;}
}
}
自動プロパティの内部
自動実装プロパティを使用した場合、コンパイラはこっそりと内部で、プロパティのアクセサからのみ、アクセスが可能なバッキングフィールドを生成します。そこにデータの保存を行なっています。
プロパティの継承
プロパティは"virtual"をつけて継承を行い、"override"をすることも可能です。
// 基底クラス
public class VirtualProp
{
public virtual int MyProperty
{
get
{
return MyProperty + 100;
}
}
}
// サブクラス
public class OverrideProp : VirtualProp
{
public override int MyProperty
{
get
{
return MyProperty + 200;
}
}
}
さらに、この他にも、インターフェースにプロパティを定義することも可能です。"abstract"として宣言することもできます。ただし、インターフェース時にsetアクセサが"private(アクセス不可)である"として省略されていた場合、インターフェースを実装するクラスでは、setアクセサの修飾子を"private"または"protected"に変更したり、setを実装して"public(アクセス可能)"とすることができてしまいます。注意して下さい。
継承先のプロパティに"new"修飾子をつけたり、"Sealed"にしたり、などなどが可能なので是非活用してみて下さい。
ただし、アクセサしか持たないクラスはあまり推奨されていません。
その他
引数を取るプロパティとしてインデクサという機能もあります。これはまた別の機会に調べて、まとめられたらなと思います。
調べた感想
先ずはここまで読んで頂いて有難うございました。現在UnityではC#6までは対応いるので、今回紹介した機能は使用することができます。
個人的には、必ず使わなくてはいけないものではなく、固有の処理を含んでいたり、読み取り専用や書き込み専用にしたい場合にはプロパティを使用するといいのではないかなァと感想です。私自身は結構、引数などでデータの受け渡しを行っているので、これまでにプロパティの恩恵を受けることは多くはなかったのですが、これを機にうまい使い方を模索してみたいと思います。
参考資料
- プロパティ (C# プログラミング ガイド)
- オブジェクト指向プログラムで
getter/setterメソッドを使わなければならない
10の理由 - プロパティ?フィールド?メンバー?C#のクラス構造のおさらい
- C#のプロパティについて調べてみた
- C# 6 の新機能
変更履歴
- かずき(@okazuki)さんの指摘により、プロパティのsetアクセサ省略機能に関する部分を修正しました。