C# の プロパティ(Property) は、クラスやstructのフィールドへのアクセスを制御する仕組みです。外部からはフィールドのように読み書きできますが、内部では get / set アクセサを通じてロジックを挟めるため、カプセル化 を保ちながら直感的な構文を実現できます。
本記事ではプロパティの基本構文を整理します。init アクセサや required 修飾子などの応用的な機能は次の記事でまとめます。
フィールドとプロパティの違い
まず、フィールドを直接公開する場合の問題点を確認します。
public class Person
{
public string Name; // public フィールド(非推奨)
}
var p = new Person();
p.Name = ""; // バリデーションなしで空文字が入る
フィールドを直接公開すると、値の検証や変更通知などのロジックを挟めません。プロパティを使うとこの問題を解決できます。
プロパティの基本構文(フル実装)
バッキングフィールド(裏側の変数)を明示的に定義し、get / set アクセサでアクセスを制御します。
public class Person
{
private string _name = ""; // バッキングフィールド
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name は空にできません。");
_name = value;
}
}
}
var p = new Person();
p.Name = "Alice"; // set アクセサが呼ばれる
Console.WriteLine(p.Name); // get アクセサが呼ばれる → Alice
p.Name = ""; // ArgumentException
value は set アクセサに暗黙的に渡される、代入された値を保持するキーワードです。
自動実装プロパティ(Auto-Implemented Property)
バリデーション等が不要な場合、バッキングフィールドをコンパイラに任せて簡潔に書けます。C# 3.0 で導入されました。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
コンパイラが裏側でバッキングフィールドを自動生成します。フィールドのように見えますが、プロパティなのでインターフェースの実装やデータバインディングにも対応できます。
自動実装プロパティの初期値(C# 6.0〜)
C# 6.0 以降、自動実装プロパティに初期値を設定できます。
public class Config
{
public int MaxRetry { get; set; } = 3;
public string Locale { get; set; } = "ja-JP";
}
読み取り専用プロパティ
外部からの書き込みを禁止し、読み取りだけを許可するパターンは複数あります。
get のみのプロパティ(C# 6.0〜)
set アクセサを省略すると、コンストラクタまたは初期値でのみ設定できる読み取り専用プロパティになります。
public class Circle
{
public double Radius { get; }
public Circle(double radius)
{
Radius = radius; // コンストラクタでのみ設定可能
}
}
var c = new Circle(5.0);
Console.WriteLine(c.Radius); // 5
// c.Radius = 10; // コンパイルエラー
private set
クラス内部からは書き換え可能だが、外部からは読み取り専用にしたい場合に使います。
public class Counter
{
public int Count { get; private set; }
public void Increment() => Count++;
}
var counter = new Counter();
counter.Increment();
Console.WriteLine(counter.Count); // 1
// counter.Count = 100; // コンパイルエラー(外部からの set は不可)
アクセサへのアクセス修飾子の詳細は Access Modifiers の記事 を参照してください。
書き込み専用プロパティ
get を省略して set のみを定義することもできますが、使用頻度は低いです。
public class Logger
{
private string _output = "";
public string Output
{
set { _output = value; }
}
}
var logger = new Logger();
logger.Output = "test";
// Console.WriteLine(logger.Output); // コンパイルエラー(get がない)
計算プロパティ(Computed Property)
バッキングフィールドを持たず、他のプロパティやフィールドから値を計算して返すプロパティです。
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
// 面積は Width と Height から計算される
public double Area
{
get { return Width * Height; }
}
}
var rect = new Rectangle { Width = 3, Height = 4 };
Console.WriteLine(rect.Area); // 12
Expression-Bodied メンバ を使うとさらに簡潔に書けます。
public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
public double Area => Width * Height; // Expression-Bodied
}
=> を使う式形式は get のみのプロパティと等価です。set は含まれません。
静的プロパティ(Static Property)
static キーワードを付けると、インスタンスではなく型に属するプロパティになります。
public class AppSettings
{
public static string Environment { get; set; } = "Development";
public static int MaxConnections { get; set; } = 10;
}
Console.WriteLine(AppSettings.Environment); // Development
AppSettings.MaxConnections = 50;
静的プロパティはインスタンスなしでアクセスでき、設定値やシングルトン的なアクセスに使われます。
プロパティとフィールドの使い分け
| 観点 | フィールド | プロパティ |
|---|---|---|
| アクセス制御 | 修飾子のみ | get/set 個別に修飾子を付けられる |
| バリデーション | 不可 | set でロジックを挟める |
| インターフェース | 定義不可 | 定義可能 |
| データバインディング | 非対応 | 対応 |
| リフレクション | FieldInfo |
PropertyInfo(別の API) |
| デバッグ | ブレークポイント不可 | get/set にブレークポイントを置ける |
一般的なガイドラインとして、公開メンバーにはプロパティを使い、フィールドは private または protected に限定することが推奨されます。
まとめ
- プロパティは
get/setアクセサでフィールドへのアクセスを制御する仕組み - 自動実装プロパティはバッキングフィールドを省略でき、C# 6.0 から初期値も設定可能
getのみで読み取り専用、private setで外部読み取り専用にできる- バッキングフィールドを持たない計算プロパティは Expression-Bodied 構文で簡潔に書ける
- 静的プロパティはインスタンスなしで型に直接アクセスする
- 公開メンバーにはフィールドではなくプロパティを使うのが C# の慣例
次の記事では init アクセサ・required 修飾子・インターフェースプロパティなど、プロパティの応用的な機能を整理します。