1. はじめに
まずはフィールドを直接使うときに起きる問題から始めよう。もしフィールドをpublicで宣言したら、プログラムのどこからでも即座に値を変えられちゃうんだ:
public class Dog
{
public string Name;
}
Dog dog = new Dog();
dog.Name = ""; // O_o ... これって「犬の名前 = 空文字列」ってことだよ!
ユーザーはNameフィールドに、意味不明な値も平気で入れられる。例えば空文字列、長すぎる名前、あるいはnullとかね。これはまるで、君の新しいIKEAの棚を誰かが組み立ててるのに、木工工場の全アクセス権を渡しちゃうようなもんだ。
覚えておいて、カプセル化っていうのは、オブジェクトが自分のデータを自分でコントロールすること。他人に内部を全部見せるんじゃなくて、特別な「ドア」— プロパティ (Properties) を用意して、そこを通してアクセスさせる。しかもチェックやログ、値の変更、他の反応もできるんだ。
2. 定義とシンタックス
プロパティは、見た目はフィールドっぽいけど、実は裏で特別なメソッドのペア:getter(値を取得)とsetter(値をセット)が動いてる。プロパティを使うと:
- データの読み書きを許可したり禁止したりできる;
- アクセス時にバリデーションやロジックを追加できる;
- 内部フィールドを隠したり、値を別の場所に保存したりもできる。
プロパティはフィールドに似てるけど、波カッコとgetとsetキーワードが中に入るのが特徴。
[アクセス修飾子] 型 プロパティ名
{
get { ... }
set { ... }
}
クラスDogの例を見てみよう:
public class Dog
{
private string _name; // 内部フィールド (private!)
public string Name
{
get { return _name; } // 「ゲッター」:名前を取得
set { _name = value; } // 「セッター」:名前をセット
}
}
メモ:アンダースコアは普通、プライベートフィールド(_name)に使う。これはC#のスタイル標準だよ。
3. プロパティの仕組み
プロパティは、オブジェクトの内部データと外の世界の間に立つ「ガードマン」みたいなもの。例を見てみよう:
Dog dog = new Dog();
dog.Name = "シャリク";
Console.WriteLine(dog.Name);
解説:
- プログラムがdog.Name = "シャリク";の行に来たとき、プロパティのsetメソッドが呼ばれる。ここで好きなチェック(例えば名前が空じゃないか)を追加できる。
- Console.WriteLine(dog.Name);のときはgetメソッドが呼ばれて、今の値を返すか、必要なら動的に計算した値を返す。
見た目は普通のフィールドみたいだけど、実は全部コントロールできるんだ!
4. なぜプロパティが「Best Practice」なのか
ほとんどの場合、オブジェクトの内部フィールドに直接アクセスはさせない。今はチェックが不要に思えても、データをプロパティで包むクセをつけておくと、ルールが変わったときにめっちゃ助かるよ。
「バリデーション」付きの例:
public class Dog
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("犬の名前は空にできないよ!");
}
_name = value;
}
}
}
これでこんなことをすると:
dog.Name = ""; // 例外が投げられる!
…変な値からオブジェクトを守れるんだ。
5. プロパティ:読み取り専用、書き込み専用、普通のプロパティ
時々、値の参照だけ許したいときがある(例えば犬の生年は変えられない)、逆にセットだけ許したい(レアだけど、何か謎なことをしたいとき)。
- 読み取り専用:getだけ書いて、setは消す。
- 書き込み専用:setだけ書いて、getは消す。
例:
public class Dog
{
private int _birthYear = 2018;
// 読み取り専用
public int BirthYear
{
get { return _birthYear; }
}
// 書き込み専用(かなりレア)
public string Secret
{
set { /* valueで何かする */ }
}
}
6. プロパティ vs フィールド
| フィールド | プロパティ | |
|---|---|---|
| シンタックス | |
|
| アクセス | 直接 | get/set経由 |
| バリデーション | なし | set/getに追加できる |
| 拡張性 | なし | いつでも変更できる |
| IDE連携 | フィールドとして見える | プロパティとして見える(フレームワークで重要) |
イラスト:プロパティの動作
sequenceDiagram
participant User as オブジェクトのユーザー
participant Dog as Dogオブジェクト
participant Field as プライベートフィールド _name
User->>Dog: dog.Name = "リジク"
Dog->>Dog: set Name("リジク")
Dog->>Field: _name = "リジク"
User->>Dog: print(dog.Name)
Dog->>Dog: get Name()
Dog->>Field: _nameを読む
Dog->>User: "リジク"を返す
7. よくあるミスと注意点
どこに落とし穴があるか見てみよう。
よくあるミスは、フィールドとプロパティを混同して、プライベートデータをうっかり外に公開しちゃうこと:
public string name; // これはフィールド!どこからでも見えちゃう、危険!
こうした方がいい:
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
staticプロパティもあって、全オブジェクト共通の値を保存できる。でも使いすぎ注意だよ。
面白い話:プロパティにgetだけあってコンストラクタがなければ、値は絶対に変えられない—これがimmutable propertyってやつで、最近の設計でよく使われる(recordやデータの不変性の講義で詳しくやるよ)。
GO TO FULL VERSION