C# の class コンストラクタは、オブジェクトが生成されるときにフィールドやプロパティを初期化するための特殊なメソッドです。C# 1.0 から存在する基本機能ですが、バージョンを重ねるごとに記述の簡潔さや安全性を高める拡張が続いています。
本記事では、C# 1.0 の基本から C# 12 の Primary Constructor まで順に整理します。
基本のコンストラクタ — C# 1.0
インスタンスコンストラクタ
クラス名と同じ名前で、戻り値の型を持たないメソッドがコンストラクタです。
class Person
{
public string Name;
public int Age;
// パラメーターありコンストラクタ
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
var p = new Person("Alice", 30);
パラメーターなしコンストラクタ(デフォルトコンストラクタ)
コンストラクタを一つも定義しなければ、コンパイラがパラメーターなしコンストラクタを暗黙に生成します。パラメーターありコンストラクタを定義すると暗黙のデフォルトコンストラクタは消えるため、new Person() を使いたい場合は明示的に定義する必要があります。
class Person
{
public string Name = "";
public int Age;
// パラメーターありコンストラクタを定義すると
// デフォルトコンストラクタは自動生成されない
public Person(string name, int age)
{
Name = name;
Age = age;
}
// 引き続き new Person() を使いたい場合は明示的に定義
public Person() { }
}
静的コンストラクタ(static constructor)
static コンストラクタはクラス単位で一度だけ実行され、静的フィールドの初期化に使います。アクセス修飾子は付けられず、パラメーターも取れません。
class AppConfig
{
public static string Version;
static AppConfig()
{
Version = "1.0.0";
Console.WriteLine("Static constructor called");
}
}
// クラスへの最初のアクセスで static コンストラクタが実行される
Console.WriteLine(AppConfig.Version); // 1.0.0
private コンストラクタ
コンストラクタを private にすることで、外部からのインスタンス生成を禁止できます。シングルトンパターンやファクトリーパターンで使われます。
class Singleton
{
private static readonly Singleton _instance = new Singleton();
private Singleton() { }
public static Singleton Instance => _instance;
}
コンストラクタチェーン — this() / base()
this() で同じクラスの別コンストラクタ、base() で基底クラスのコンストラクタを呼び出せます。
class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
// this() で別コンストラクタに委譲
public Person(string name) : this(name, 0) { }
}
class Animal
{
public string Species;
public Animal(string species)
{
Species = species;
}
}
class Dog : Animal
{
public string Name;
// base() で基底クラスのコンストラクタを呼び出す
public Dog(string name) : base("Dog")
{
Name = name;
}
}
基底クラスにパラメーターありコンストラクタしかない場合、派生クラスは base() で明示的に呼び出す必要があります。省略するとコンパイルエラーになります。
オブジェクト初期化子 — C# 3.0
C# 3.0 で導入されたオブジェクト初期化子により、コンストラクタのオーバーロードを減らせるようになりました。
class Person
{
public string Name { get; set; } = "";
public int Age { get; set; }
public string Email { get; set; } = "";
}
// コンストラクタを定義せずにプロパティを初期化
var p = new Person { Name = "Alice", Age = 30, Email = "alice@example.com" };
任意の組み合わせでプロパティを設定できるため、「名前だけ」「名前と年齢だけ」などの場面ごとにコンストラクタを用意する必要がなくなります。
Optional / Named Parameters — C# 4.0
C# 4.0 で導入されたオプション引数・名前付き引数はコンストラクタにも適用できます。
class ConnectionConfig
{
public string Host { get; }
public int Port { get; }
public bool UseSsl { get; }
public ConnectionConfig(string host, int port = 443, bool useSsl = true)
{
Host = host;
Port = port;
UseSsl = useSsl;
}
}
// 省略可能なパラメーターのおかげでオーバーロードが不要
var c1 = new ConnectionConfig("example.com");
var c2 = new ConnectionConfig("example.com", 8080);
var c3 = new ConnectionConfig("example.com", useSsl: false); // 名前付き引数
名前付き引数については [C#] Named Arguments(名前付き引数) も参照してください。
Expression-Bodied コンストラクタ / ファイナライザ — C# 7.0
C# 6.0 でメソッドや読み取り専用プロパティに導入された Expression-Bodied 構文(=>)が、C# 7.0 でコンストラクタ・ファイナライザ・get / set アクセサーにも拡張されました。
コンストラクタ
本体が 1 文で済む場合に => で簡潔に書けます。
class Greeter
{
public string Message { get; }
// Expression-Bodied コンストラクタ
public Greeter(string name) => Message = $"Hello, {name}!";
}
従来のブロック構文と比較すると次のとおりです。
// 従来
public Greeter(string name)
{
Message = $"Hello, {name}!";
}
// Expression-Bodied(C# 7.0)
public Greeter(string name) => Message = $"Hello, {name}!";
ファイナライザ
class Resource
{
~Resource() => Console.WriteLine("Finalized");
}
get / set アクセサー
class Temperature
{
private double _celsius;
public double Celsius
{
get => _celsius;
set => _celsius = value >= -273.15
? value
: throw new ArgumentOutOfRangeException();
}
}
Expression-Bodied メンバの詳細は [C#] Expression-Bodied メンバ・ローカル関数・static ローカル関数 を参照してください。
out 変数宣言 — C# 7.0
C# 7.0 では out 引数を呼び出し時にその場で宣言できるようになりました。コンストラクタの中でも活用できます。
class ParsedConfig
{
public int Timeout { get; }
public bool IsValid { get; }
public ParsedConfig(string timeoutStr)
{
// out 変数をその場で宣言(C# 7.0)
IsValid = int.TryParse(timeoutStr, out int value);
Timeout = IsValid ? value : 0;
}
}
var config = new ParsedConfig("30");
Console.WriteLine(config.Timeout); // 30
Console.WriteLine(config.IsValid); // True
C# 7.0 より前は、事前に変数を宣言してから out に渡す必要がありました。
// C# 6.0 以前
int value;
bool ok = int.TryParse(timeoutStr, out value);
メソッドパラメータの out / ref / in の詳細は [C#] メソッドパラメータの修飾子 — out / ref / in / params を参照してください。
target-typed new(new())— C# 9.0
C# 9.0 では、型が推論できる文脈で new() と書けるようになりました。コンストラクタ呼び出しの冗長さが軽減されます。
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
// 型名を省略できる
Point p = new(3, 4);
// フィールド・プロパティの初期化でも使える
class Canvas
{
private Point _origin = new(0, 0);
private List<Point> _points = new();
}
メソッド引数でも型が確定していれば使えます。
void Draw(Point p) { /* ... */ }
Draw(new(10, 20));
required メンバー — C# 11
C# 11 で導入された required 修飾子をプロパティに付けると、オブジェクト初期化子での設定を強制できます。コンストラクタで初期化を忘れる心配がなくなります。
class User
{
public required string Name { get; init; }
public required string Email { get; init; }
public int Age { get; init; }
}
// OK
var user = new User { Name = "Alice", Email = "alice@example.com", Age = 30 };
// コンパイルエラー:required メンバーが未設定
// var user = new User { Age = 30 };
required はコンストラクタとの組み合わせも可能です。SetsRequiredMembers 属性を付けたコンストラクタは、required メンバーの設定を免除されます。
using System.Diagnostics.CodeAnalysis;
class User
{
public required string Name { get; init; }
public required string Email { get; init; }
public User() { }
[SetsRequiredMembers]
public User(string name, string email)
{
Name = name;
Email = email;
}
}
// どちらも OK
var u1 = new User("Alice", "alice@example.com");
var u2 = new User { Name = "Bob", Email = "bob@example.com" };
Primary Constructor — C# 12
C# 12(.NET 8)で、record だけでなく通常の class や struct にも Primary Constructor(主コンストラクタ)が使えるようになりました。クラス名の直後にパラメーターリストを書く構文です。
基本構文
class Person(string name, int age)
{
public string Name { get; } = name;
public int Age { get; } = age;
}
var p = new Person("Alice", 30);
Console.WriteLine(p.Name); // Alice
パラメーターはフィールド・プロパティの初期化子やメソッド本体からキャプチャできます。
DI(依存性注入)での活用
Primary Constructor は DI コンテナ経由で注入される依存をスッキリ受け取るのに適しています。
// 従来
class OrderService
{
private readonly IOrderRepository _repo;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository repo, ILogger<OrderService> logger)
{
_repo = repo;
_logger = logger;
}
public void Process(int orderId)
{
_logger.LogInformation("Processing order {Id}", orderId);
_repo.Save(orderId);
}
}
// Primary Constructor(C# 12)
class OrderService(IOrderRepository repo, ILogger<OrderService> logger)
{
public void Process(int orderId)
{
logger.LogInformation("Processing order {Id}", orderId);
repo.Save(orderId);
}
}
ボイラープレートが大幅に減り、「受け取って使うだけ」のパターンが簡潔になります。
record との違い
record の Primary Constructor はプロパティを自動生成しますが、class の Primary Constructor はパラメーターをキャプチャするだけでプロパティは生成しません。プロパティとして公開したい場合は明示的に定義する必要があります。
// record:Name と Age が自動的に public プロパティになる
public record PersonRecord(string Name, int Age);
// class:パラメーターはキャプチャされるだけ
public class PersonClass(string name, int age)
{
// 明示的にプロパティにしないと外部からアクセスできない
public string Name { get; } = name;
public int Age { get; } = age;
}
追加コンストラクタとの共存
Primary Constructor を持つクラスに追加のコンストラクタを定義する場合、this() で Primary Constructor を呼び出す必要があります。
class Point(int x, int y)
{
public int X { get; } = x;
public int Y { get; } = y;
// 追加コンストラクタは this() で Primary Constructor に委譲する
public Point() : this(0, 0) { }
}
まとめ
| 機能 | 導入バージョン | 要点 |
|---|---|---|
| インスタンスコンストラクタ / static / private | C# 1.0 | コンストラクタの基本形 |
コンストラクタチェーン(this() / base()) |
C# 1.0 | コンストラクタ間の委譲 |
| オブジェクト初期化子 | C# 3.0 | オーバーロードの削減 |
| Optional / Named Parameters | C# 4.0 | デフォルト値と名前付き引数 |
| Expression-Bodied コンストラクタ | C# 7.0 | => で 1 行コンストラクタ |
out 変数宣言 |
C# 7.0 | out 引数をその場で宣言 |
target-typed new() |
C# 9.0 | 型推論でクラス名を省略 |
required メンバー |
C# 11 | 初期化子での設定を強制 |
| Primary Constructor | C# 12 | クラス名に直接パラメーターリストを記述 |
C# 1.0 の時点で十分に実用的だったコンストラクタですが、Expression-Bodied・target-typed new・Primary Constructor などの進化により、ボイラープレートは着実に減り続けています。特に C# 12 の Primary Constructor は DI パターンとの相性が良く、実務で大きな恩恵があります。