C#のクラスとオブジェクト — フィールド・プロパティ・コンストラクタ
この章の目次開く
C#はクラスを中心に組み立てる言語です。JavaScriptでは関数とオブジェクトリテラルだけでも書けますが、C#ではデータと処理をクラスにまとめるのが標準的な書き方になります。
この章では、クラスの定義から、C#特有の「プロパティ」という仕組みまでを扱います。
クラスを定義して使う
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Greet() => $"こんにちは、{Name}です";
}var user = new User();
user.Name = "田中";
user.Age = 30;
Console.WriteLine(user.Greet());new でインスタンスを作り、. でメンバーにアクセスします。ここまではJavaScriptのクラスと同じです。
オブジェクト初期化子
生成と同時にプロパティを設定できます。
var user = new User { Name = "田中", Age = 30 };フィールドとプロパティ
C#には、値を保持する仕組みが2つあります。
| フィールド | プロパティ | |
|---|---|---|
| 正体 | 変数そのもの | 値の出し入れを行うメソッドの組 |
| 書き方 | private string name; | public string Name { get; set; } |
| 公開するか | 通常は private | 外部への公開はこちらを使う |
| 途中で処理を挟めるか | できない | できる(検証・変換・通知など) |
{ get; set; } は自動実装プロパティと呼ばれ、コンパイラが裏側にフィールドを用意してくれます。あとから検証を足したくなったら、次のように書き換えられます。
public class User
{
private int _age;
public int Age
{
get => _age;
set
{
if (value < 0) throw new ArgumentException("年齢は0以上です");
_age = value;
}
}
}set の中では、代入された値が value という名前で使えます。
学習者最初から { get; set; } と書いておけば、後で困らないということですか?
その通りです。呼び出し側は user.Age = 30; と書くだけなので、あとから内部実装を変えても呼び出し側のコードを直さずに済みます。これがフィールドを直接公開しない最大の理由です。
読み取り専用にする
| 書き方 | 変更できるタイミング |
|---|---|
{ get; set; } | いつでも |
{ get; init; } | オブジェクト初期化子まで |
{ get; } + コンストラクタで設定 | コンストラクタ内のみ |
{ get; private set; } | クラス内部からのみ |
public class Order
{
public string Id { get; init; }
public DateTime CreatedAt { get; init; }
}
var order = new Order { Id = "A-001", CreatedAt = DateTime.Now };
// order.Id = "A-002"; ← コンパイルエラーinit は「生成時だけ設定でき、その後は変更できない」プロパティを作る仕組みです。意図しない書き換えを防げるため、実務では積極的に使われます。

コンストラクタ
インスタンス生成時に必要な値を受け取るには、コンストラクタを定義します。
public class User
{
public string Name { get; }
public int Age { get; }
public User(string name, int age)
{
Name = name;
Age = age;
}
}
var user = new User("田中", 30);C# 12以降では、クラス宣言に直接引数を書くプライマリコンストラクタも使えます。
public class User(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}コンストラクタは引数違いで複数定義でき、this(...) で別のコンストラクタに処理を委譲できます。
public User(string name) : this(name, 0)
{
}アクセス修飾子
| 修飾子 | どこから見えるか |
|---|---|
public | どこからでも |
private | そのクラスの中だけ(既定値) |
protected | そのクラスと派生クラス |
internal | 同じプロジェクト(アセンブリ)内 |
迷ったら private から始め、必要になったときに広げるのが安全です。
静的メンバー
インスタンスを作らずに使うメンバーには static を付けます。
public class TaxCalculator
{
public const decimal Rate = 0.1m;
public static decimal Add(decimal price) => price * (1 + Rate);
}
var total = TaxCalculator.Add(1000m);インスタンスごとの状態を持たない計算処理やユーティリティは、static にすると呼び出しが簡潔になります。ただし状態を static で持つと、アプリ全体で共有される変数になり、テストや並行処理で問題を起こしやすくなります。
ToStringを上書きする
すべてのクラスは暗黙的に object を継承しており、ToString() を持っています。既定ではクラス名が返るだけなので、必要に応じて上書きします。
public override string ToString() => $"{Name}({Age})";override については C#の継承とインターフェース で扱います。
よくあるハマりどころ
プロパティを初期化せずにnullのまま使う
public class User
{
public string Name { get; set; } // 警告: null の可能性
}Nullable が有効なプロジェクトでは、初期化されない参照型のプロパティに警告が出ます。required を付けて必須にするか、= string.Empty; で初期化するか、string? にするかを選びます。詳しくは C#のnull安全 で扱います。
publicフィールドで公開してしまう
public string Name; // フィールド(避ける)
public string Name { get; set; } // プロパティ(推奨)見た目の使い勝手は同じですが、後から検証を挟めない・一部のライブラリが認識しないなどの差があります。
インスタンスをコピーしたつもりが共有されていた
クラスは参照型なので、代入しても実体は1つのままです。中身の複製が必要なら、新しいインスタンスを作って値を移すか、record の with 式を使います(C#のモダン構文)。
先生値型と参照型の話 がここで効いてくる。クラスは全部参照型だと覚えておこう。
ちゃんと使うためのポイント
- 外部に公開する値はプロパティ(
{ get; set; })で定義する - 生成後に変えたくない値は
initかgetのみにする - コンストラクタは「そのオブジェクトが成立するために必要な値」を受け取る
- アクセス修飾子は
privateから始めて必要な範囲だけ広げる staticは状態を持たない処理に使い、可変な状態は持たせない- クラスは参照型。代入してもコピーされない
次の章では、クラス同士の関係を表す継承とインターフェースに進みます。C#の型システムで最も設計判断が問われる部分です。