C#コンストラクタとは?基本の書き方から初期化・オーバーロード・base/thisの使い方まで初心者向けに解説

はじめに

C#でクラスを使ったプログラムを書くとき、必ず理解しておきたい仕組みのひとつが「コンストラクタ」です。英語では「constructor」と書き、C# constructorについて調べている方の多くは、「インスタンスを作るときに何が起きているのか」「初期値はどこで設定すればよいのか」「thisやbaseはどう使うのか」といった疑問を持っているのではないでしょうか。

コンストラクタは、オブジェクトを生成するときに自動的に呼び出される特別な処理です。フィールドやプロパティに初期値を設定したり、必須の値を受け取ったり、親クラスの初期化処理を呼び出したりできます。

この記事では、C#コンストラクタの基本的な書き方から、初期化、オーバーロード、this、base、アクセス修飾子、特殊なコンストラクタ、よくある間違いまで、初心者にもわかりやすく解説します。

1. C#コンストラクタとは?

C#のコンストラクタとは、クラスからオブジェクトを作成するときに自動で実行される特別なメンバーです。通常のメソッドと似た見た目をしていますが、戻り値を書かない、クラス名と同じ名前で定義するなど、いくつかの重要なルールがあります。

たとえば、Personクラスからnew Person()でインスタンスを作ると、そのタイミングでPersonクラスのコンストラクタが呼び出されます。

C#
class Person
{
public Person()
{
Console.WriteLine("Personオブジェクトが作成されました");
}
}

Person person = new Person();

このコードでは、new Person()を実行したときにコンストラクタが呼ばれ、メッセージが表示されます。

1-1. コンストラクタの役割は「オブジェクト生成時の初期化」

コンストラクタの主な役割は、オブジェクト生成時に必要な初期化を行うことです。

たとえば、ユーザー情報を表すクラスであれば、名前や年齢などの値を最初に設定できます。

C#
class User
{
public string Name { get; set; }
public int Age { get; set; }

public User(string name, int age)
{
Name = name;
Age = age;
}
}

このようにしておくと、Userオブジェクトを作る時点で必要な情報を渡せます。

C#
User user = new User("Taro", 25);

オブジェクトを作った後に値を設定し忘れるリスクを減らせるため、コンストラクタは安全な初期化に役立ちます。

1-2. メソッドとの違い

コンストラクタはメソッドに似ていますが、通常のメソッドとは異なります。

通常のメソッドは、戻り値の型を書き、任意の名前を付けて、必要なタイミングで呼び出します。

C#
class Sample
{
public void ShowMessage()
{
Console.WriteLine("メソッドです");
}
}

一方、コンストラクタは戻り値を書かず、クラス名と同じ名前で定義します。

C#
class Sample
{
public Sample()
{
Console.WriteLine("コンストラクタです");
}
}

また、通常のメソッドはsample.ShowMessage()のように明示的に呼び出しますが、コンストラクタはnew Sample()のタイミングで自動的に呼び出されます。

1-3. コンストラクタが呼び出されるタイミング

コンストラクタは、new演算子でインスタンスを作成するときに呼び出されます。

C#
Sample sample = new Sample();

この1行では、主に次のような流れが発生します。

  1. Sample型のオブジェクトを作成する領域が用意される

  2. フィールドやプロパティ初期化子が評価される

  3. コンストラクタが実行される

  4. 作成されたインスタンスが変数sampleに代入される

初心者のうちは、「newするとコンストラクタが動く」と覚えると理解しやすいです。

1-4. 初心者が押さえるべきポイント

C#コンストラクタで初心者がまず押さえるべきポイントは、次のとおりです。

コンストラクタは、クラス名と同じ名前で定義します。戻り値は書きません。newでインスタンスを作成したときに自動的に呼び出されます。主な目的は、フィールドやプロパティを初期化することです。必要に応じて、引数を受け取るコンストラクタも作れます。

この基本を理解しておくと、クラス設計やオブジェクト指向プログラミングがぐっと理解しやすくなります。

2. C#コンストラクタの基本的な書き方

ここからは、C#コンストラクタの基本的な書き方を見ていきましょう。まずは構文を理解し、その後で具体的なコード例を確認します。

2-1. コンストラクタの構文

コンストラクタの基本構文は次のとおりです。

C#
class クラス名
{
public クラス名()
{
// 初期化処理
}
}

たとえば、Carクラスのコンストラクタは次のように書きます。

C#
class Car
{
public Car()
{
Console.WriteLine("車を作成しました");
}
}

public Car()の部分がコンストラクタです。

2-2. クラス名と同じ名前で定義する

コンストラクタは、必ずクラス名と同じ名前で定義します。

C#
class Book
{
public Book()
{
Console.WriteLine("Bookのコンストラクタです");
}
}

この場合、クラス名がBookなので、コンストラクタ名もBookです。

次のように、クラス名と異なる名前にするとコンストラクタではなく通常のメソッドとして扱われます。

C#
class Book
{
public void CreateBook()
{
Console.WriteLine("これは通常のメソッドです");
}
}

コンストラクタとして定義したい場合は、クラス名と完全に同じ名前にする必要があります。

2-3. 戻り値を書かない理由

コンストラクタには戻り値を書きません。voidも書きません。

正しい書き方は次のとおりです。

C#
class Product
{
public Product()
{
Console.WriteLine("商品を作成しました");
}
}

間違った書き方は次のとおりです。

C#
class Product
{
public void Product()
{
Console.WriteLine("これはコンストラクタではありません");
}
}

voidを書いてしまうと、これはコンストラクタではなく、Productという名前の通常メソッドになります。

コンストラクタの目的は「オブジェクトを初期化すること」であり、値を返す処理ではありません。そのため、戻り値の型を書かないというルールになっています。

2-4. new演算子でインスタンスを作成する流れ

コンストラクタは、new演算子でインスタンスを作成するときに呼び出されます。

C#
Product product = new Product();

このコードでは、右辺のnew Product()によってProductクラスのインスタンスが作成されます。その際、Productクラスのコンストラクタが実行されます。

つまり、コンストラクタは自分で直接呼び出すものではなく、インスタンス生成の流れの中で自動的に呼ばれるものです。

2-5. 基本コード例で理解する

次のコードで、コンストラクタの動きを確認してみましょう。

C#
class Student
{
public Student()
{
Console.WriteLine("Studentコンストラクタが呼び出されました");
}
}

class Program
{
static void Main()
{
Student student = new Student();
}
}

実行結果は次のようになります。

Studentコンストラクタが呼び出されました

Mainメソッド内ではnew Student()しか書いていませんが、そのタイミングでStudentクラスのコンストラクタが呼ばれていることがわかります。

3. コンストラクタでフィールドやプロパティを初期化する方法

コンストラクタのもっとも一般的な使い方は、フィールドやプロパティの初期化です。オブジェクトを作成した直後から正しい状態で使えるようにするため、必要な値をコンストラクタ内で設定します。

3-1. フィールドを初期化する例

まずはフィールドを初期化する例です。

C#
class Counter
{
private int count;

public Counter()
{
count = 0;
}

public void ShowCount()
{
Console.WriteLine(count);
}
}

この例では、Counterオブジェクトが作成されたときに、フィールドcount0を設定しています。

C#
Counter counter = new Counter();
counter.ShowCount();

実行すると、0が表示されます。

フィールドはクラス内部で保持する値です。コンストラクタで初期化しておくことで、オブジェクト生成直後から想定した状態で利用できます。

3-2. プロパティを初期化する例

プロパティもコンストラクタで初期化できます。

C#
class Member
{
public string Name { get; set; }
public int Point { get; set; }

public Member()
{
Name = "未設定";
Point = 0;
}
}

この例では、Memberオブジェクトを作成すると、Nameには"未設定"Pointには0が設定されます。

C#
Member member = new Member();

Console.WriteLine(member.Name);
Console.WriteLine(member.Point);

プロパティは外部からアクセスしやすい値なので、初期状態を明確にしておくと扱いやすくなります。

3-3. 引数を受け取って値を設定する

コンストラクタは引数を受け取ることもできます。外部から渡された値を使って初期化したい場合に便利です。

C#
class Employee
{
public string Name { get; set; }
public int Age { get; set; }

public Employee(string name, int age)
{
Name = name;
Age = age;
}
}

使う側では、次のように値を渡します。

C#
Employee employee = new Employee("Sato", 30);

Console.WriteLine(employee.Name);
Console.WriteLine(employee.Age);

実行結果は次のようになります。

Sato
30

このように、コンストラクタの引数を使うと、オブジェクト生成時に必要な値を確実に設定できます。

3-4. 初期値を設定するメリット

コンストラクタで初期値を設定するメリットは、オブジェクトが不完全な状態になるのを防げることです。

たとえば、名前が必須のUserクラスがあるとします。コンストラクタで名前を受け取る設計にすれば、名前が未設定のUserオブジェクトを作りにくくなります。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name;
}
}

この設計では、Userを作るときに必ず名前を渡す必要があります。

C#
User user = new User("Yamada");

必須の情報をコンストラクタで受け取ることで、コードの安全性と可読性が高まります。

3-5. コンストラクタ初期化とプロパティ初期化子の違い

C#では、プロパティ初期化子を使って初期値を設定することもできます。

C#
class Setting
{
public string Theme { get; set; } = "Light";
}

この場合、Settingオブジェクトを作成すると、Themeには"Light"が設定されます。

一方、コンストラクタ初期化では、条件に応じた初期化や引数を使った初期化ができます。

C#
class Setting
{
public string Theme { get; set; }

public Setting(string theme)
{
Theme = theme;
}
}

プロパティ初期化子は、固定の初期値を設定したい場合に向いています。コンストラクタは、引数を使いたい場合や、複数の値をまとめて初期化したい場合に向いています。

4. デフォルトコンストラクタと引数付きコンストラクタ

C#のコンストラクタには、引数を持たないデフォルトコンストラクタと、引数を持つコンストラクタがあります。それぞれの違いを理解しておくことは非常に重要です。

4-1. デフォルトコンストラクタとは

デフォルトコンストラクタとは、引数を持たないコンストラクタのことです。

C#
class Animal
{
public Animal()
{
Console.WriteLine("Animalを作成しました");
}
}

この場合、次のように引数なしでインスタンスを作成できます。

C#
Animal animal = new Animal();

デフォルトコンストラクタは、特別な値を渡さずにオブジェクトを作りたい場合に使います。

4-2. コンストラクタを定義しない場合の挙動

クラスにコンストラクタを1つも定義しない場合、C#では引数なしのコンストラクタが自動的に用意されます。

C#
class Animal
{
public string Name { get; set; }
}

このクラスでは明示的にコンストラクタを書いていませんが、次のようにインスタンスを作成できます。

C#
Animal animal = new Animal();

これは、C#が暗黙的に引数なしのコンストラクタを用意してくれるためです。

4-3. 引数付きコンストラクタを定義する方法

引数付きコンストラクタは、オブジェクト生成時に値を受け取りたい場合に使います。

C#
class Animal
{
public string Name { get; set; }

public Animal(string name)
{
Name = name;
}
}

使う側では、次のように引数を渡します。

C#
Animal animal = new Animal("Dog");
Console.WriteLine(animal.Name);

引数付きコンストラクタを使うことで、生成時に必要なデータをセットできます。

4-4. 引数付きコンストラクタを定義するとデフォルトコンストラクタはどうなるか

注意すべき点として、引数付きコンストラクタを1つでも定義すると、C#は引数なしのデフォルトコンストラクタを自動生成しなくなります。

C#
class Animal
{
public string Name { get; set; }

public Animal(string name)
{
Name = name;
}
}

この状態で次のように書くと、コンパイルエラーになります。

C#
Animal animal = new Animal();

なぜなら、Animal()という引数なしのコンストラクタが存在しないからです。

引数なしでも作成できるようにしたい場合は、明示的にデフォルトコンストラクタを追加します。

C#
class Animal
{
public string Name { get; set; }

public Animal()
{
Name = "Unknown";
}

public Animal(string name)
{
Name = name;
}
}

4-5. よくあるコンパイルエラーと対処法

初心者がよく遭遇するエラーは、「引数付きコンストラクタを定義した後に、引数なしでnewしてしまう」ケースです。

C#
class User
{
public string Name { get; set; }

public User(string name)
{
Name = name;
}
}

User user = new User(); // エラー

対処法は2つあります。

1つ目は、必要な引数を渡すことです。

C#
User user = new User("Taro");

2つ目は、引数なしのコンストラクタを追加することです。

C#
class User
{
public string Name { get; set; }

public User()
{
Name = "未設定";
}

public User(string name)
{
Name = name;
}
}

どちらを選ぶかは、そのクラスにとって値が必須かどうかで判断します。

5. コンストラクタのオーバーロード

C#では、同じクラス内に複数のコンストラクタを定義できます。これをコンストラクタのオーバーロードといいます。

5-1. オーバーロードとは

オーバーロードとは、同じ名前のメソッドやコンストラクタを、引数の数や型を変えて複数定義することです。

コンストラクタの場合、名前は必ずクラス名と同じになるため、引数の違いによってどのコンストラクタを呼び出すかが決まります。

C#
class Sample
{
public Sample()
{
}

public Sample(string message)
{
}
}

このように、引数なしと引数ありのコンストラクタを同じクラスに定義できます。

5-2. 引数の数や型を変えて複数定義する

コンストラクタのオーバーロードでは、引数の数や型を変える必要があります。

C#
class Product
{
public Product()
{
}

public Product(string name)
{
}

public Product(string name, int price)
{
}
}

この例では、次の3通りの生成方法が可能です。

C#
Product product1 = new Product();
Product product2 = new Product("Book");
Product product3 = new Product("Book", 1200);

C#は渡された引数の数や型を見て、呼び出すコンストラクタを判断します。

5-3. オーバーロードのコード例

具体的な例を見てみましょう。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }

public Product()
{
Name = "未設定";
Price = 0;
}

public Product(string name)
{
Name = name;
Price = 0;
}

public Product(string name, int price)
{
Name = name;
Price = price;
}
}

使い方は次のとおりです。

C#
Product product1 = new Product();
Product product2 = new Product("Pen");
Product product3 = new Product("Notebook", 500);

Console.WriteLine($"{product1.Name}: {product1.Price}");
Console.WriteLine($"{product2.Name}: {product2.Price}");
Console.WriteLine($"{product3.Name}: {product3.Price}");

実行結果は次のようになります。

未設定: 0
Pen: 0
Notebook: 500

複数の生成パターンを用意したい場合に、コンストラクタのオーバーロードは便利です。

5-4. オーバーロードを使うメリット

コンストラクタをオーバーロードするメリットは、利用者が状況に応じてインスタンスを作成できることです。

たとえば、商品名だけを指定したい場合もあれば、商品名と価格の両方を指定したい場合もあります。そのようなときに複数のコンストラクタを用意しておくと、柔軟に対応できます。

また、初期値をクラス側で補完できるため、使う側のコードもシンプルになります。

C#
Product product = new Product("Pen");

この場合、価格はクラス側で0に設定されるため、呼び出し側は商品名だけを渡せば済みます。

5-5. オーバーロード時に注意すべき設計

オーバーロードは便利ですが、増やしすぎるとコードが読みにくくなります。

たとえば、次のように似たようなコンストラクタが大量にあると、どれを使えばよいか迷いやすくなります。

C#
public Product(string name) { }
public Product(int price) { }
public Product(string name, int price) { }
public Product(int price, string name) { }

特に、引数の順番が違うだけのオーバーロードは混乱の原因になります。

コンストラクタのオーバーロードを使うときは、次の点を意識しましょう。

必要なパターンだけを用意すること。引数の意味がわかりやすいこと。重複する初期化処理を増やしすぎないこと。複雑になりそうな場合は、オプション引数やファクトリメソッドも検討すること。

6. thisを使ったコンストラクタの呼び出し

C#では、thisキーワードを使って同じクラス内の別のコンストラクタを呼び出せます。これにより、コンストラクタの重複コードを減らせます。

6-1. thisキーワードの基本

thisは、現在のインスタンス自身を表すキーワードです。

たとえば、フィールド名と引数名が同じ場合、thisを使うと区別できます。

C#
class User
{
private string name;

public User(string name)
{
this.name = name;
}
}

この例では、左側のthis.nameがフィールド、右側のnameがコンストラクタの引数です。

プロパティでも同じように使えます。

C#
class User
{
public string Name { get; set; }

public User(string name)
{
this.Name = name;
}
}

ただし、プロパティ名と引数名を大文字・小文字で分ける場合は、thisを書かなくても意味が通じます。

C#
public User(string name)
{
Name = name;
}

6-2. this()で同じクラスの別コンストラクタを呼び出す

this()を使うと、同じクラス内の別のコンストラクタを呼び出せます。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }

public Product() : this("未設定", 0)
{
}

public Product(string name, int price)
{
Name = name;
Price = price;
}
}

このコードでは、引数なしのコンストラクタが呼ばれると、: this("未設定", 0)によって、Product(string name, int price)が呼び出されます。

C#
Product product = new Product();

結果として、Nameには"未設定"Priceには0が設定されます。

6-3. コンストラクタの重複コードを減らす方法

複数のコンストラクタに同じ初期化処理を書くと、修正が必要になったときに手間が増えます。

たとえば、次のコードでは初期化処理が重複しています。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }

public Product()
{
Name = "未設定";
Price = 0;
}

public Product(string name)
{
Name = name;
Price = 0;
}

public Product(string name, int price)
{
Name = name;
Price = price;
}
}

this()を使うと、共通の初期化処理を1つのコンストラクタにまとめられます。

C#
class Product
{
public string Name { get; set; }
public int Price { get; set; }

public Product() : this("未設定", 0)
{
}

public Product(string name) : this(name, 0)
{
}

public Product(string name, int price)
{
Name = name;
Price = price;
}
}

このように書くと、実際に値を設定する処理は最後のコンストラクタに集約されます。

6-4. thisを使った実践コード例

もう少し実践的な例として、会員情報を表すMemberクラスを見てみましょう。

C#
class Member
{
public string Name { get; }
public string Rank { get; }
public int Point { get; }

public Member(string name) : this(name, "Normal", 0)
{
}

public Member(string name, string rank) : this(name, rank, 0)
{
}

public Member(string name, string rank, int point)
{
Name = name;
Rank = rank;
Point = point;
}
}

使い方は次のとおりです。

C#
Member member1 = new Member("Taro");
Member member2 = new Member("Hanako", "Gold");
Member member3 = new Member("Sato", "Platinum", 3000);

この設計では、最低限必要な名前だけを渡すこともできますし、ランクやポイントまで指定することもできます。最終的な初期化処理は1か所にまとまっているため、保守しやすいコードになります。

6-5. this利用時の注意点

this()を使って別のコンストラクタを呼び出す場合、呼び出しはコンストラクタ本体の前に書く必要があります。

C#
public Product() : this("未設定", 0)
{
}

次のように、コンストラクタ本体の中でthis(...)を呼び出すことはできません。

C#
public Product()
{
this("未設定", 0); // エラー
}

また、this()base()を同じコンストラクタで同時に指定することはできません。どちらか一方だけを使います。

7. baseを使った親クラスのコンストラクタ呼び出し

C#で継承を使う場合、子クラスのコンストラクタから親クラスのコンストラクタを呼び出すことがあります。そのときに使うのがbaseキーワードです。

7-1. baseキーワードの基本

baseは、親クラスを参照するためのキーワードです。

メソッドやプロパティにアクセスする場合にも使えますが、コンストラクタではbase()という形で親クラスのコンストラクタを呼び出します。

C#
class Animal
{
public Animal()
{
Console.WriteLine("Animalコンストラクタ");
}
}

class Dog : Animal
{
public Dog() : base()
{
Console.WriteLine("Dogコンストラクタ");
}
}

この例では、Dogクラスのコンストラクタから、親クラスであるAnimalのコンストラクタを呼び出しています。

7-2. 継承時のコンストラクタ呼び出し順

継承時には、親クラスのコンストラクタが先に呼び出され、その後で子クラスのコンストラクタが実行されます。

C#
class Animal
{
public Animal()
{
Console.WriteLine("Animal");
}
}

class Dog : Animal
{
public Dog()
{
Console.WriteLine("Dog");
}
}

Dog dog = new Dog();

実行結果は次のようになります。

Animal
Dog

子クラスのオブジェクトを作成するには、まず親クラス部分を初期化する必要があります。そのため、親クラスのコンストラクタが先に実行されます。

7-3. base()で親クラスのコンストラクタに値を渡す

親クラスに引数付きコンストラクタがある場合、子クラスからbase()を使って値を渡します。

C#
class Animal
{
public string Name { get; }

public Animal(string name)
{
Name = name;
}
}

class Dog : Animal
{
public Dog(string name) : base(name)
{
}
}

この例では、Dogのコンストラクタで受け取ったnameを、base(name)によってAnimalのコンストラクタへ渡しています。

C#
Dog dog = new Dog("Pochi");
Console.WriteLine(dog.Name);

実行結果は次のようになります。

Pochi

7-4. baseを使った実践コード例

実践的な例として、社員を表すクラスを考えてみましょう。

C#
class Person
{
public string Name { get; }
public int Age { get; }

public Person(string name, int age)
{
Name = name;
Age = age;
}
}

class Employee : Person
{
public string Department { get; }

public Employee(string name, int age, string department)
: base(name, age)
{
Department = department;
}
}

使い方は次のとおりです。

C#
Employee employee = new Employee("Suzuki", 35, "Sales");

Console.WriteLine(employee.Name);
Console.WriteLine(employee.Age);
Console.WriteLine(employee.Department);

このコードでは、NameAgeは親クラスPersonで初期化し、Departmentは子クラスEmployeeで初期化しています。

継承関係では、共通項目を親クラスにまとめ、固有項目を子クラスで扱うと設計がわかりやすくなります。

7-5. 継承でよくあるエラーと対処法

継承時によくあるエラーは、親クラスに引数なしコンストラクタがないのに、子クラスでbase()の指定を忘れるケースです。

C#
class Person
{
public Person(string name)
{
}
}

class Employee : Person
{
public Employee()
{
}
}

このコードはエラーになります。Personには引数なしコンストラクタが存在しないため、Employeeのコンストラクタから親クラスのコンストラクタを正しく呼び出せないからです。

対処法は、子クラスのコンストラクタでbaseに必要な値を渡すことです。

C#
class Employee : Person
{
public Employee() : base("未設定")
{
}
}

または、親クラスに引数なしコンストラクタを追加します。

C#
class Person
{
public Person()
{
}

public Person(string name)
{
}
}

どちらが適切かは、親クラスの初期化に値が必須かどうかで判断します。

8. コンストラクタのアクセス修飾子

コンストラクタには、publicprivateprotectedinternalなどのアクセス修飾子を付けられます。アクセス修飾子によって、そのコンストラクタをどこから呼び出せるかが変わります。

8-1. publicコンストラクタ

publicコンストラクタは、どこからでも呼び出せます。もっとも一般的に使われるコンストラクタです。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name;
}
}

この場合、クラスの外から自由にインスタンスを作成できます。

C#
User user = new User("Taro");

外部から通常どおりオブジェクトを作成してほしいクラスでは、publicコンストラクタを使います。

8-2. privateコンストラクタ

privateコンストラクタは、クラスの外部から呼び出せません。

C#
class Utility
{
private Utility()
{
}

public static void ShowMessage()
{
Console.WriteLine("Hello");
}
}

このようにすると、外部からnew Utility()できなくなります。

C#
// Utility utility = new Utility(); // エラー

privateコンストラクタは、インスタンス化させたくないクラスや、シングルトンパターンなどで使われます。

8-3. protectedコンストラクタ

protectedコンストラクタは、そのクラス自身と派生クラスから呼び出せます。

C#
class BaseClass
{
protected BaseClass()
{
Console.WriteLine("BaseClass");
}
}

class DerivedClass : BaseClass
{
public DerivedClass() : base()
{
Console.WriteLine("DerivedClass");
}
}

BaseClassを外部から直接作成させたくないが、継承先では使わせたい場合に便利です。

C#
DerivedClass obj = new DerivedClass();

抽象クラスや基底クラスでよく使われます。

8-4. internalコンストラクタ

internalコンストラクタは、同じアセンブリ内からのみ呼び出せます。

C#
class Repository
{
internal Repository()
{
}
}

同じプロジェクト内ではインスタンス化できますが、別のアセンブリからは呼び出せません。

ライブラリ開発などで、「外部には公開したくないが、内部では生成したい」という場合に使われます。

8-5. アクセス修飾子の使い分け

コンストラクタのアクセス修飾子は、クラスをどのように使わせたいかで選びます。

外部から自由にインスタンス化してよい場合はpublicを使います。クラス外から生成させたくない場合はprivateを使います。継承先からのみ生成させたい場合はprotectedを使います。同じアセンブリ内だけで生成させたい場合はinternalを使います。

コンストラクタのアクセス範囲を適切に制御すると、意図しない使われ方を防ぎ、クラス設計を明確にできます。

9. 特殊なコンストラクタ

C#には、通常のインスタンスコンストラクタ以外にも、いくつか特殊なコンストラクタの考え方があります。ここでは、静的コンストラクタ、privateコンストラクタ、コピーコンストラクタ風の実装、レコードや構造体のコンストラクタについて解説します。

9-1. 静的コンストラクタ

静的コンストラクタは、クラスそのものを初期化するためのコンストラクタです。インスタンスではなく、静的メンバーの初期化に使われます。

C#
class AppConfig
{
public static string AppName { get; }

static AppConfig()
{
AppName = "SampleApp";
Console.WriteLine("静的コンストラクタが呼び出されました");
}
}

静的コンストラクタにはアクセス修飾子を書きません。また、引数も指定できません。

C#
Console.WriteLine(AppConfig.AppName);

静的コンストラクタは、クラスが初めて使用されるタイミングで実行されます。

通常のコンストラクタはインスタンスを作るたびに呼び出されますが、静的コンストラクタは型の初期化に使われる点が大きな違いです。

9-2. privateコンストラクタとシングルトン

privateコンストラクタは、シングルトンパターンで使われることがあります。シングルトンとは、インスタンスを1つだけに制限する設計パターンです。

C#
class Singleton
{
private static readonly Singleton instance = new Singleton();

public static Singleton Instance
{
get { return instance; }
}

private Singleton()
{
}
}

使う側は、new Singleton()ではなく、Singleton.Instanceからインスタンスを取得します。

C#
Singleton obj = Singleton.Instance;

コンストラクタをprivateにすることで、外部から勝手にインスタンスを作れないようにしています。

9-3. コピーコンストラクタ風の実装

C#にはC++のような言語仕様としてのコピーコンストラクタはありませんが、似た形のコンストラクタを自分で定義できます。

C#
class Person
{
public string Name { get; }
public int Age { get; }

public Person(string name, int age)
{
Name = name;
Age = age;
}

public Person(Person other)
{
Name = other.Name;
Age = other.Age;
}
}

使い方は次のとおりです。

C#
Person person1 = new Person("Taro", 20);
Person person2 = new Person(person1);

Console.WriteLine(person2.Name);
Console.WriteLine(person2.Age);

このように、既存のオブジェクトをもとに新しいオブジェクトを作成できます。

ただし、参照型のプロパティを持つ場合は、浅いコピーと深いコピーの違いに注意が必要です。

9-4. レコードや構造体におけるコンストラクタ

C#では、クラスだけでなく構造体にもコンストラクタを定義できます。

C#
struct Point
{
public int X { get; }
public int Y { get; }

public Point(int x, int y)
{
X = x;
Y = y;
}
}

使い方は次のとおりです。

C#
Point point = new Point(10, 20);

また、レコードでは、簡潔な構文で値を保持する型を定義できます。

C#
public record Person(string Name, int Age);

この場合、NameAgeを受け取るコンストラクタが自動的に用意されます。

C#
Person person = new Person("Taro", 25);

レコードは、値の比較や不変データの表現に向いています。

9-5. 通常のコンストラクタとの違い

通常のコンストラクタは、インスタンスを作成するときに呼び出され、フィールドやプロパティを初期化します。

一方、静的コンストラクタは型そのものの初期化に使われます。privateコンストラクタは外部からのインスタンス化を制限します。コピーコンストラクタ風の実装は、既存オブジェクトをもとに新しいオブジェクトを作るために使います。レコードのコンストラクタは、簡潔にデータを保持する型を定義するために使われます。

それぞれ役割が異なるため、目的に応じて使い分けることが大切です。

10. C#コンストラクタのよくある間違い

C#コンストラクタは基本を理解すれば難しくありませんが、初心者がつまずきやすいポイントもあります。ここでは、よくある間違いと対処法を紹介します。

10-1. 戻り値を書いてしまう

もっともよくある間違いのひとつが、コンストラクタにvoidを書いてしまうことです。

C#
class User
{
public void User()
{
Console.WriteLine("コンストラクタのつもり");
}
}

これはコンストラクタではなく、Userという名前の通常メソッドです。

正しくは次のように書きます。

C#
class User
{
public User()
{
Console.WriteLine("正しいコンストラクタ");
}
}

コンストラクタには戻り値を書かない、と覚えておきましょう。

10-2. クラス名とコンストラクタ名が一致していない

コンストラクタ名はクラス名と完全に一致している必要があります。

C#
class Customer
{
public User()
{
}
}

このように、クラス名がCustomerなのにコンストラクタ名がUserになっているとエラーになります。

正しくは次のとおりです。

C#
class Customer
{
public Customer()
{
}
}

クラス名を変更したときは、コンストラクタ名も忘れずに確認しましょう。

10-3. 引数付きコンストラクタ定義後に引数なしでnewしてしまう

引数付きコンストラクタを定義すると、引数なしのデフォルトコンストラクタは自動生成されません。

C#
class Item
{
public string Name { get; }

public Item(string name)
{
Name = name;
}
}

Item item = new Item(); // エラー

対処法は、引数を渡すことです。

C#
Item item = new Item("Pen");

または、引数なしのコンストラクタを追加します。

C#
class Item
{
public string Name { get; }

public Item()
{
Name = "未設定";
}

public Item(string name)
{
Name = name;
}
}

10-4. thisとbaseの使い方を混同する

thisは同じクラスのメンバーや別コンストラクタを参照するために使います。

C#
public Product() : this("未設定", 0)
{
}

一方、baseは親クラスのコンストラクタやメンバーを参照するために使います。

C#
public Employee(string name) : base(name)
{
}

this()は同じクラスの別コンストラクタを呼び出すもの、base()は親クラスのコンストラクタを呼び出すものです。

また、1つのコンストラクタで: this(...): base(...)を同時に書くことはできません。

10-5. 初期化処理を複雑にしすぎる

コンストラクタには初期化処理を書けますが、何でも詰め込めばよいわけではありません。

たとえば、コンストラクタ内で時間のかかるファイル読み込み、ネットワーク通信、データベースアクセスなどを行うと、インスタンス作成が重くなり、テストもしにくくなります。

C#
class DataService
{
public DataService()
{
// 重い処理を大量に書くのは避けたい
}
}

コンストラクタでは、できるだけオブジェクトの状態を整えるための軽い初期化にとどめるのが基本です。重い処理は、専用のメソッドや非同期処理、ファクトリなどに分けるとよいでしょう。

11. コンストラクタ設計のベストプラクティス

コンストラクタはただ書ければよいものではなく、設計の良し悪しがコード全体の読みやすさや保守性に影響します。ここでは、C#コンストラクタを設計するときの考え方を紹介します。

11-1. 必須項目はコンストラクタで受け取る

オブジェクトとして成立するために必ず必要な値は、コンストラクタで受け取るのがおすすめです。

C#
class Order
{
public string OrderId { get; }
public DateTime CreatedAt { get; }

public Order(string orderId)
{
OrderId = orderId;
CreatedAt = DateTime.Now;
}
}

この例では、注文IDが必須項目です。コンストラクタで受け取ることで、注文IDがないOrderオブジェクトを作りにくくできます。

必須項目を後からプロパティで設定する設計にすると、設定忘れによって不完全なオブジェクトができる可能性があります。

11-2. 任意項目はプロパティや初期化子で扱う

必須ではない項目は、コンストラクタではなくプロパティや初期化子で扱うと柔軟です。

C#
class User
{
public string Name { get; }
public string Email { get; set; } = "";
public string Address { get; set; } = "";

public User(string name)
{
Name = name;
}
}

使う側では、必要に応じて任意項目を設定できます。

C#
User user = new User("Taro")
{
Email = "taro@example.com",
Address = "Tokyo"
};

必須項目と任意項目を分けることで、コンストラクタの引数が増えすぎるのを防げます。

11-3. コンストラクタ内に重い処理を書きすぎない

コンストラクタは、オブジェクトを初期化するための処理を書く場所です。重い処理を詰め込みすぎると、インスタンス生成に時間がかかり、例外処理やテストも難しくなります。

避けたい例は次のようなコードです。

C#
class ReportService
{
public ReportService()
{
// 大量のファイル読み込み
// 外部APIへのアクセス
// データベース接続
}
}

このような処理は、必要なタイミングで呼び出すメソッドに分けるのが望ましいです。

C#
class ReportService
{
public ReportService()
{
// 軽い初期化だけにする
}

public void LoadData()
{
// 重い処理はこちらに分ける
}
}

コンストラクタは、できるだけシンプルに保ちましょう。

11-4. オーバーロードよりオプション引数を使うべきケース

コンストラクタのオーバーロードが増えすぎる場合は、オプション引数を検討できます。

C#
class Product
{
public string Name { get; }
public int Price { get; }
public string Category { get; }

public Product(string name, int price = 0, string category = "未分類")
{
Name = name;
Price = price;
Category = category;
}
}

この場合、次のように複数の呼び出し方ができます。

C#
Product product1 = new Product("Pen");
Product product2 = new Product("Notebook", 500);
Product product3 = new Product("Desk", 10000, "Furniture");

似たようなオーバーロードが多い場合は、オプション引数を使うことでコードを簡潔にできます。

ただし、引数の意味がわかりにくくなる場合や、複雑な初期化が必要な場合は、無理にオプション引数へまとめないほうがよいこともあります。

11-5. 読みやすく保守しやすい初期化設計

読みやすく保守しやすいコンストラクタにするには、次の考え方が重要です。

必須項目だけをコンストラクタで受け取る。任意項目はプロパティ初期化子やオブジェクト初期化子を使う。コンストラクタの引数を増やしすぎない。重複する初期化処理はthis()でまとめる。継承時はbase()で親クラスの初期化を明確にする。重い処理はコンストラクタから分離する。

コンストラクタは、クラスの使いやすさを決める入口です。利用者が迷わず、間違いにくい形を意識して設計しましょう。

12. C#コンストラクタに関するよくある質問

最後に、C#コンストラクタについて初心者がよく疑問に思う点をQ&A形式で整理します。

12-1. コンストラクタは必ず書く必要がある?

必ず書く必要はありません。

クラスにコンストラクタを1つも定義しない場合、C#は引数なしのデフォルトコンストラクタを自動的に用意します。

C#
class Sample
{
public string Name { get; set; }
}

Sample sample = new Sample();

ただし、生成時に必ず設定したい値がある場合や、初期化処理が必要な場合は、明示的にコンストラクタを書くべきです。

12-2. コンストラクタに戻り値は必要?

コンストラクタに戻り値は不要です。voidも書きません。

C#
class Sample
{
public Sample()
{
}
}

次のように書くと、コンストラクタではなく通常のメソッドになってしまいます。

C#
class Sample
{
public void Sample()
{
}
}

C# constructorでは、戻り値を書かないことが基本ルールです。

12-3. コンストラクタは複数作れる?

はい、作れます。

引数の数や型を変えれば、同じクラス内に複数のコンストラクタを定義できます。これをコンストラクタのオーバーロードといいます。

C#
class User
{
public User()
{
}

public User(string name)
{
}

public User(string name, int age)
{
}
}

ただし、数を増やしすぎると使い方がわかりにくくなるため、必要なものだけを定義しましょう。

12-4. this()とbase()は同時に使える?

1つのコンストラクタで、: this(...): base(...)を同時に指定することはできません。

C#
public Sample() : this("test")
{
}

または、

C#
public Sample() : base()
{
}

のように、どちらか一方を使います。

this()は同じクラスの別コンストラクタを呼び出すものです。base()は親クラスのコンストラクタを呼び出すものです。役割が異なるため、混同しないようにしましょう。

12-5. コンストラクタと初期化子はどちらを使うべき?

固定の初期値を設定するだけなら、プロパティ初期化子がシンプルです。

C#
class User
{
public string Role { get; set; } = "Member";
}

一方、外部から値を受け取って初期化したい場合や、必須項目を保証したい場合はコンストラクタが向いています。

C#
class User
{
public string Name { get; }

public User(string name)
{
Name = name;
}
}

目安として、必須項目はコンストラクタ、固定の初期値や任意項目は初期化子やプロパティで扱うと、わかりやすい設計になります。

まとめ

C#コンストラクタは、オブジェクト生成時に初期化処理を行うための重要な仕組みです。クラス名と同じ名前で定義し、戻り値を書かず、newでインスタンスを作成したタイミングで自動的に呼び出されます。

コンストラクタでは、フィールドやプロパティに初期値を設定したり、引数を受け取って必須データを初期化したりできます。引数なしのデフォルトコンストラクタ、引数付きコンストラクタ、オーバーロード、this()による同一クラス内のコンストラクタ呼び出し、base()による親クラスのコンストラクタ呼び出しを理解すると、クラス設計の幅が広がります。

また、publicprivateprotectedinternalなどのアクセス修飾子を使うことで、インスタンス化できる範囲を制御できます。静的コンストラクタやシングルトン、コピーコンストラクタ風の実装、レコードや構造体のコンストラクタも、目的に応じて使い分けることが大切です。

初心者のうちは、まず「コンストラクタはオブジェクトを作るときに呼ばれる初期化処理」と覚えましょう。そのうえで、必須項目はコンストラクタで受け取り、任意項目はプロパティや初期化子で扱うようにすると、読みやすく安全なC#コードを書けるようになります。