C#のコンストラクタは、オブジェクト生成直後に初期化を行うための特別なメソッドです。
本記事では、コンストラクタの基本文法と初期化の実例を、C#初心者でも迷わないように丁寧に解説します。
実行結果付きのサンプルを多く掲載し、実務でよく使う初期化(コレクション、日時、ID、readonlyなど)までひと通りカバーします。
C#のコンストラクタ入門(初心者向け)
コンストラクタの役割と初期化の流れ
コンストラクタとは何か
コンストラクタはクラス名と同名の特別なメソッドで、インスタンス生成時(new
の直後)に必ず1回だけ実行されます。
主にフィールドやプロパティの初期値設定、依存オブジェクトの受け渡し、簡単なパラメータ検証などに使います。
実行のタイミング
new クラス名(...)
を呼ぶと、メモリ確保→コンストラクタ実行→参照が返るという順に処理されます。
オブジェクト初期化子(new クラス名 { ... }
)を使う場合でも、必ず先にコンストラクタが実行され、その後にプロパティ代入が起きます。
デフォルトコンストラクタ(引数なし)
自動生成と明示定義
クラス内にコンストラクタを1つも定義しない場合、C#は引数なしのデフォルトコンストラクタを自動で用意します。
自分で1つでもコンストラクタを定義すると自動生成は行われませんので、必要なら明示的に定義します。
using System;
public class Player
{
// フィールド(簡単のためpublicにしています)
public string Name;
public int Hp;
// 明示的なデフォルトコンストラクタ(引数なし)
public Player()
{
// ゲーム開始時の初期値
Name = "NoName";
Hp = 100;
Console.WriteLine("Player() が実行されました");
}
}
public class Program
{
public static void Main()
{
// new の瞬間に Player() が実行されます
var p = new Player();
Console.WriteLine($"Name={p.Name}, Hp={p.Hp}");
}
}
Player() が実行されました
Name=NoName, Hp=100
いつ明示的に書くべきか
初期値に意味がある場合やログ出力、軽い準備処理(例: キャッシュの暖気)をしたい場合は、デフォルトコンストラクタを明示すると意図が明確になります。
引数ありコンストラクタで値を受け取る
外部から初期値を注入する
引数ありコンストラクタでは、生成時に外部から必要な値を受け取り、不完全な状態のオブジェクトが生まれないように設計できます。
using System;
public class Player
{
public string Name { get; }
public int Hp { get; private set; }
// 引数ありコンストラクタ
public Player(string name, int hp)
{
// 簡単な検証(負のHPは無効)
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name は必須です", nameof(name));
if (hp < 0) throw new ArgumentOutOfRangeException(nameof(hp), "hp は0以上にしてください");
Name = name;
Hp = hp;
Console.WriteLine($"Player(name={name}, hp={hp}) が実行されました");
}
}
public class Program
{
public static void Main()
{
var p = new Player("Alice", 80);
Console.WriteLine($"Name={p.Name}, Hp={p.Hp}");
}
}
Player(name=Alice, hp=80) が実行されました
Name=Alice, Hp=80
コンストラクタの基本文法と書き方
クラス名と同名で定義する
構文の基本
コンストラクタには戻り値を記述しません。
戻り値を書いてしまうと通常メソッドとして扱われ、コンストラクタとしては認識されません。
public class Sample
{
// 正しいコンストラクタ(クラス名と同名、戻り値なし)
public Sample()
{
// 初期化処理
}
// 誤りの例: 戻り値を付けると普通のメソッドになる
// public void Sample() { } // これはコンストラクタではありません
}
フィールド/プロパティを初期化する
役割の違いと初期化の場所
フィールドはクラス内部のデータ保持、プロパティは外部公開のためのアクセス制御に向いています。
外部に見せるデータはプロパティ、内部の実装詳細はフィールドとし、コンストラクタで適切に初期化します。
public class Account
{
// 内部状態(フィールド)
private decimal _balance;
// 外部公開(プロパティ)
public string Owner { get; }
public decimal Balance => _balance;
public Account(string owner, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(owner)) throw new ArgumentException("owner は必須です", nameof(owner));
if (initialBalance < 0) throw new ArgumentOutOfRangeException(nameof(initialBalance));
Owner = owner; // 読み取り専用プロパティの初期化
_balance = initialBalance; // フィールドの初期化
}
}
最小コードの例(C#)
必要最小限の実行サンプル
もっともシンプルな「データを1つ初期化して出力する」例です。
using System;
public class Greeting
{
public string Message { get; }
public Greeting()
{
Message = "Hello, Constructor!";
}
}
public class Program
{
public static void Main()
{
var g = new Greeting();
Console.WriteLine(g.Message);
}
}
Hello, Constructor!
コンストラクタ初期化のサンプル集
基本型の初期化(int, string)
数値と文字列の典型的な初期化
プリミティブ型の初期化は最もよく使います。
既定値に意味を持たせ、不正値にならないようにしましょう。
using System;
public class Counter
{
public int Start { get; }
public string Label { get; }
public Counter()
{
Start = 1; // 1から数え始める
Label = "Default"; // 既定のラベル
}
}
public class Program
{
public static void Main()
{
var c = new Counter();
Console.WriteLine($"Start={c.Start}, Label={c.Label}");
}
}
Start=1, Label=Default
プロパティに初期値を設定
自動実装プロパティと初期化の組み合わせ
自動実装プロパティに対して、コンストラクタで分かりやすく初期化します。
using System;
public class UserProfile
{
public string Name { get; }
public int Age { get; }
public bool IsActive { get; private set; }
public UserProfile(string name, int age)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("name は必須です", nameof(name));
if (age < 0) throw new ArgumentOutOfRangeException(nameof(age));
Name = name;
Age = age;
IsActive = true; // 生成直後は有効
}
}
public class Program
{
public static void Main()
{
var u = new UserProfile("Bob", 20);
Console.WriteLine($"{u.Name} ({u.Age}) Active={u.IsActive}");
}
}
Bob (20) Active=True
コレクション(List)を初期化
空のリストを必ず用意しておく
コレクションはnullにしないのが実務では大切です。
生成直後から安全に追加できるように初期化します。
using System;
using System.Collections.Generic;
public class TodoList
{
public List<string> Items { get; }
public TodoList()
{
Items = new List<string>(); // null防止
}
public void Add(string item) => Items.Add(item);
}
public class Program
{
public static void Main()
{
var t = new TodoList();
t.Add("Learn C#");
t.Add("Write constructors");
Console.WriteLine(string.Join(", ", t.Items));
}
}
Learn C#, Write constructors
日時やIDの初期値を設定
生成時刻と一意IDの採番
ログや監査に役立つため、作成日時や一意識別子を自動付与するのはよくあるパターンです。
using System;
public class AuditEntry
{
public Guid Id { get; }
public DateTime CreatedAtUtc { get; }
public string Message { get; }
public AuditEntry(string message)
{
if (string.IsNullOrWhiteSpace(message)) throw new ArgumentException("message は必須です", nameof(message));
Id = Guid.NewGuid(); // 一意ID
CreatedAtUtc = DateTime.UtcNow; // UTCで記録
Message = message;
}
}
public class Program
{
public static void Main()
{
var a = new AuditEntry("User logged in");
Console.WriteLine($"Id={a.Id}, CreatedAtUtc={a.CreatedAtUtc:O}, Message={a.Message}");
}
}
Id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, CreatedAtUtc=2025-01-01T12:34:56.7890123Z, Message=User logged in
注: 実行時の日時やGUIDは環境により異なります。
readonlyフィールドを初期化
不変な値を安全に保持する
readonlyフィールドはコンストラクタかフィールド初期化子でのみ代入可能です。
不変性が必要な設定値に向いています。
using System;
public class Config
{
public readonly string EnvironmentName; // 生成後は変更不可
public string Endpoint { get; } // プロパティも実質不変にできる
public Config(string env, string endpoint)
{
if (string.IsNullOrWhiteSpace(env)) throw new ArgumentException(nameof(env));
if (string.IsNullOrWhiteSpace(endpoint)) throw new ArgumentException(nameof(endpoint));
EnvironmentName = env; // readonlyの初期化
Endpoint = endpoint;
}
}
public class Program
{
public static void Main()
{
var cfg = new Config("Production", "https://api.example.com");
Console.WriteLine($"{cfg.EnvironmentName} -> {cfg.Endpoint}");
}
}
Production -> https://api.example.com
コンストラクタの注意点とコツ
フィールド初期化子との違い
それぞれの実行順序と責務
フィールド初期化子は、コンストラクタ本体の前に実行されます。
固定の既定値はフィールド初期化子、引数に依存する初期化や検証はコンストラクタに分担すると読みやすくなります。
public class Example
{
// フィールド初期化子(固定値)
private readonly int _retryCount = 3;
public string Name { get; }
public Example(string name)
{
// ここはフィールド初期化子の後に実行される
Name = string.IsNullOrWhiteSpace(name) ? "Unknown" : name;
}
}
オブジェクト初期化子との使い分け
コンストラクタとオブジェクト初期化子の順序
オブジェクト初期化子はコンストラクタ実行後にプロパティへ代入します。
必須値や不整合チェックはコンストラクタで行い、任意の上書きはオブジェクト初期化子が向いています。
public class Person
{
public string Name { get; }
public int Age { get; private set; }
public Person(string name)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException(nameof(name));
Name = name; // 必須
Age = 0; // 既定
}
}
public class Program
{
public static void Main()
{
// コンストラクタで必須のNameを満たし、オブジェクト初期化子でAgeを上書き
var p = new Person("Carol") { /* コンストラクタ後に実行 */ };
// Age の上書きはプロパティsetterが必要です。上の例では private set のため外からは変更不可。
}
}
上記例ではAge
にprivate set
があるため、オブジェクト初期化子での上書きはできません。
必要に応じてpublic set
を設けるか、引数ありコンストラクタで受け取りましょう。
次の表は、3つの初期化手段の違いをまとめたものです。
手段 | 実行タイミング | 向いている用途 | 注意点 |
---|---|---|---|
フィールド初期化子 | コンストラクタ本体の前 | 固定の既定値、簡単な初期化 | 複雑な検証や例外は避ける |
コンストラクタ | new 直後(必ず1回) | 必須値受け取り、検証、依存注入 | テストしやすい小さな処理に留める |
オブジェクト初期化子 | コンストラクタの後 | 任意のプロパティ設定 | setterが必要。必須値には不向き |
new 時に必ず実行される点
初期化の一貫性を担保する
コンストラクタはnew
での生成時に必ず実行されるため、初期化漏れを防げます。
ファクトリメソッドなどを使う場合でも、最終的にnew
が呼ばれてコンストラクタが実行される設計にしておくと、状態の一貫性が保たれます。
簡単なパラメータ検証(ガード)
例外で不正な生成を防ぐ
必須パラメータはガード節でチェックし、不正なら例外を投げて生成を止めるのが安全です。
using System;
public class Email
{
public string Address { get; }
public Email(string address)
{
// 最低限の検証
if (string.IsNullOrWhiteSpace(address)) throw new ArgumentNullException(nameof(address));
if (!address.Contains("@")) throw new ArgumentException("メールアドレスの形式が不正です", nameof(address));
Address = address;
}
}
public class Program
{
public static void Main()
{
try
{
// 不正なメールアドレスなので例外
var e = new Email("invalid-address");
}
catch (Exception ex)
{
Console.WriteLine($"例外: {ex.GetType().Name}: {ex.Message}");
}
}
}
例外: ArgumentException: メールアドレスの形式が不正です (Parameter 'address')
.NET 6以降ではArgumentNullException.ThrowIfNull
やstring.IsNullOrWhiteSpace
を組み合わせると簡潔に書けます。
まとめ
本記事では、C#のコンストラクタの役割、基本文法、そして実践的な初期化パターンを具体例とともに解説しました。
引数なしと引数ありの両方を使い分け、必須値はコンストラクタで受け取り、固定値はフィールド初期化子、任意設定はオブジェクト初期化子という整理をしておくと、初期化ロジックが明確になります。
さらに、日時やGUID、コレクション、readonlyの扱いを押さえることで、生成直後から安全で一貫したオブジェクトを提供できます。
まずは小さなクラスから、new
時に必要な初期化を確実に行う練習を重ねてみてください。