C#初心者必見!コンストラクタとオーバーロードの基本を完全マスター

C#でのプログラミング学習、順調に進んでいますか?

クラスやオブジェクトといった概念に触れ始めると、
次に出会うのが「コンストラクタ」や「オーバーロード」といったキーワードではないでしょうか。

これらはオブジェクト指向プログラミングにおいて非常に重要な役割を果たしますが、
特に初学者にとっては少し複雑に感じられるかもしれません。

しかし、ご安心ください!
この記事では、C#プログラミングの基本でありながら、
多くの初心者がつまずきやすい「コンストラクタ」と「オーバーロード」について、
基礎から丁寧に、そして分かりやすく解説します。

これらの概念をしっかりと理解することで、
より柔軟で効率的な、そして何よりも「美しい」コードを書けるようになるはずです。

この記事を読むことで、あなたは以下のメリットを得られます。

  • コンストラクタの役割と基本的な使い方をマスターできる
  • オーバーロードの概念とメリットを理解し、活用できるようになる
  • コンストラクタとオーバーロードを組み合わせた実践的なテクニックを学べる
  • C#のコードの可読性と保守性を向上させるヒントが得られる

さあ、一緒にC#のコンストラクタとオーバーロードの理解し、
プログラミングスキルを一段階レベルアップさせましょう!

記事のポイント

  • コンストラクタはオブジェクト生成時に初期化処理を行う特別なメソッドです。
  • 引数なし、引数ありなど、複数のコンストラクタを定義できます(これをオーバーロードと呼びます)。
  • thisキーワードを使用すると、同じクラス内の別のコンストラクタを呼び出すことができます。
  • baseキーワードを使用すると、親クラス(基底クラス)のコンストラクタを呼び出すことができます。
  • オーバーロードにより、同じ名前で引数の型や数が異なるメソッドを複数定義することが可能になり、コードの柔軟性が向上します。
  • コンストラクタを明示的に書かない場合、引数なしのデフォルトコンストラクタがコンパイラによって自動的に生成されることがあります(条件あり)。

\ 2025年注目のフリーランスエージェント3社比較 /

収入を上げたい会社員」や「フリーランスでも安定して稼ぎたい人」にはフリーランスエージェントがオススメです。

スクロールできます
フリーランス
エージェント
特徴オススメ度リンク
登録者No1!!IT・Web業界特化のエージェントサービス
リモートでの参画率91%以上
迷ったらここで間違いなし
市場分析ツールで案件数の推移等が見れる

詳細ページ
給与保障制度あり
保障が正社員並みだから、会社員から転向する人は特にオススメ!
フリーランスの働き方と正社員並みの保障というイイトコどりができる!
リモートワーク案件に強み

詳細ページ
契約や手数料が全てオープンで手数料が安くなる仕組みがあるから、手数料が気になる人にオススメ!
健康面や教育面でのサポートも充実
業務系案件に強み

詳細ページ
複数サイトで情報収集するのがオススメ!

目次

【C#】 コンストラクタの基本を学ぶ

まずは、C#プログラミングの基礎となる「コンストラクタ」について深く掘り下げていきましょう。
コンストラクタとは何か、どのように書くのか、そして書かなかった場合はどうなるのか、といった疑問を一つずつ解消していきます。

【C#】コンストラクタとは?オブジェクト初期化の役割をわかりやすく解説

C#におけるコンストラクタ(Constructor)とは…

クラスのインスタンス(オブジェクト)が新しく生成される際に、自動的に呼び出される特別なメソッドのことです。その主な役割は、オブジェクトが持つフィールド(メンバ変数)を適切な初期値で設定すること、つまり「初期化」です。

例えば、RPGゲームに登場するキャラクターを考えてみましょう。
新しいキャラクター(オブジェクト)を作成する際、そのキャラクターの名前、HP、MPといったステータスを初期設定する必要があります。
このような初期設定を行うのがコンストラクタの仕事です。

public class Player
{
    public string Name;
    public int Hp;
    public int Mp;

    // これがコンストラクタです
    public Player(string name, int initialHp, int initialMp)
    {
        Name = name;
        Hp = initialHp;
        Mp = initialMp;
        Console.WriteLine($"プレイヤー「{Name}」が生成されました。(HP: {Hp}, MP: {Mp})");
    }
}

public class Game
{
    public static void Main(string[] args)
    {
        // new演算子でPlayerオブジェクトを生成する際にコンストラクタが呼び出される
        Player hero = new Player("勇者", 100, 50);
        Player wizard = new Player("魔法使い", 70, 120);
    }
}

上記の例では、Playerクラスに
Player(string name, int initialHp, int initialMp)というコンストラクタを定義しています。

new Player("勇者", 100, 50)のようにオブジェクトを生成すると、
このコンストラクタが自動的に呼び出され、NameHpMpフィールドが渡された値で初期化されます。

コンストラクタを適切に利用することで、
オブジェクトが不完全な状態で使用されるのを防ぎ、プログラムの安定性を高めることができます。

オブジェクト指向プログラミングにおいて、コンストラクタの理解は必須と言えるでしょう。

【C#】コンストラクタの書き方と引数:基本からしっかり理解しよう

C#でコンストラクタを定義する際の基本的なルールは以下の通りです。

  1. メソッド名がクラス名と完全に同じであること。
  2. 戻り値を持たないこと(voidすら記述しません)。
  3. 通常、アクセス修飾子として public を指定しますが、
    private など他のアクセス修飾子も利用可能です(特定のデザインパターンで利用されます)。

コンストラクタには、通常のメソッドと同様に引数(ひきすう)を指定することができます。

これにより、オブジェクトを生成する際に外部から初期値を渡し、
それに基づいてオブジェクトを初期化することが可能になります。

public class Book
{
    public string Title;
    public string Author;
    public int PageCount;

    // 引数を持つコンストラクタ
    public Book(string title, string author, int pageCount)
    {
        Title = title;
        Author = author;
        PageCount = pageCount;
    }

    // 引数を持たないコンストラクタ(デフォルトコンストラクタとも呼ばれる)
    public Book()
    {
        Title = "タイトル未設定";
        Author = "著者不明";
        PageCount = 0;
    }
}

public class Library
{
    public static void Main(string[] args)
    {
        // 引数ありコンストラクタで初期化
        Book book1 = new Book("C#入門", "山田太郎", 300);
        Console.WriteLine($"タイトル: {book1.Title}, 著者: {book1.Author}, ページ数: {book1.PageCount}");

        // 引数なしコンストラクタで初期化
        Book book2 = new Book();
        Console.WriteLine($"タイトル: {book2.Title}, 著者: {book2.Author}, ページ数: {book2.PageCount}");
    }
}

上記のBookクラスでは、
3つの引数(title, author, pageCount)を取るコンストラクタと、
引数を持たないコンストラクタの2種類を定義しています。

このように、引数リストが異なる同じ名前のメソッド(コンストラクタも含む)を複数定義することを
オーバーロード」と呼びます(詳細は後述します)。

引数を持つコンストラクタを定義することで、
オブジェクト生成時に必須の情報を確実に設定させることができ、
より安全で柔軟なオブジェクトの初期化が実現できます。

【C#】コンストラクタを書かない場合はどうなる?暗黙のコンストラクタとは?

C#では、クラス内にコンストラクタを1つも明示的に定義しなかった場合
コンパイラが自動的に引数なしの「デフォルトコンストラクタ(暗黙のコンストラクタ)」を生成します。

このデフォルトコンストラクタは、アクセス修飾子が public で、引数がなく、
中身には何の処理も記述されていません。

単純にオブジェクトを生成する機能だけを持っています。

public class Item
{
    public string Name;
    public int Price;

    // コンストラクタを何も定義していない
}

public class Shop
{
    public static void Main(string[] args)
    {
        // Itemクラスにはコンストラクタを定義していないが、
        // デフォルトコンストラクタが自動生成されるため、
        // new Item() のように引数なしでインスタンス化できる。
        Item apple = new Item();
        apple.Name = "りんご";
        apple.Price = 100;

        Console.WriteLine($"商品名: {apple.Name}, 価格: {apple.Price}円");
    }
}

上記の Item クラスには、開発者が明示的にコンストラクタを記述していません。

しかし、new Item() という形でインスタンスを生成できています。
これは、コンパイラが自動的に以下のようなデフォルトコンストラクタを生成してくれているためです。

// コンパイラが自動生成するデフォルトコンストラクタ(イメージ)
// public Item()
// {
// }
いしとぅ

重要な注意点があります。
クラス内に1つでもコンストラクタを明示的に定義すると、このデフォルトコンストラクタは自動生成されなくなります

public class Product
{
    public string Code;

    // 引数ありのコンストラクタを定義
    public Product(string code)
    {
        Code = code;
    }
}

public class Store
{
    public static void Main(string[] args)
    {
        // Product p1 = new Product(); // これはコンパイルエラーになる!
                                   // なぜなら、引数ありコンストラクタを定義したため、
                                   // デフォルトコンストラクタは自動生成されないから。

        Product p2 = new Product("XYZ123"); // こちらはOK
        Console.WriteLine($"商品コード: {p2.Code}");
    }
}

もし、引数ありのコンストラクタを定義した上で、引数なしのコンストラクタも使用したい場合は、別途、引数なしのコンストラクタを明示的に定義する必要があります。

public class Product
{
    public string Code;

    // 引数ありのコンストラクタ
    public Product(string code)
    {
        Code = code;
    }

    // 引数なしのコンストラクタも使いたいので、明示的に定義
    public Product()
    {
        Code = "N/A"; // 例えばデフォルト値を設定する
    }
}

このデフォルトコンストラクタの自動生成のルールを理解しておくことは、予期せぬコンパイルエラーを避けるために非常に重要です。

【C#】コンストラクタから別のコンストラクタを呼び出す方法(コンストラクタ初期化子 this)

同じクラス内に複数のコンストラクタが定義されている場合
(つまり、コンストラクタがオーバーロードされている場合)、
一方のコンストラクタからもう一方のコンストラクタを呼び出すことができます。

これには「コンストラクタ初期化子」の this キーワードを使用します。

this(...) の形式で、呼び出したいコンストラクタの引数リストに一致するものを指定します。
処理の共通化やコードの重複を避けるために非常に有効なテクニックです。

例えば、引数の数が異なる複数のコンストラクタがあり、一部の初期化処理が共通している場合、
引数が最も多いコンストラクタに処理を集約し、他のコンストラクタからは this を使ってそれを呼び出す、といった設計が考えられます。

public class Rectangle
{
    public int Width;
    public int Height;
    public string Color;

    // メインとなるコンストラクタ (3つの引数を取る)
    public Rectangle(int width, int height, string color)
    {
        Width = width;
        Height = height;
        Color = color;
        Console.WriteLine($"幅:{Width}, 高さ:{Height}, 色:{Color} の長方形を作成");
    }

    // 2つの引数を取るコンストラクタ (色はデフォルトで "白" とする)
    // this(...) を使って上記の3引数コンストラクタを呼び出す
    public Rectangle(int width, int height) : this(width, height, "白")
    {
        Console.WriteLine("2引数コンストラクタ経由");
        // ここでは追加の初期化処理などを行える
    }

    // 1つの引数を取るコンストラクタ (正方形として扱い、色はデフォルトで "黒" とする)
    public Rectangle(int sideLength) : this(sideLength, sideLength, "黒")
    {
        Console.WriteLine("1引数コンストラクタ経由 (正方形)");
    }
}

public class DrawingApp
{
    public static void Main(string[] args)
    {
        Rectangle r1 = new Rectangle(10, 5, "赤");
        // 出力: 幅:10, 高さ:5, 色:赤 の長方形を作成

        Console.WriteLine("---");

        Rectangle r2 = new Rectangle(7, 3);
        // 出力:
        // 幅:7, 高さ:3, 色:白 の長方形を作成
        // 2引数コンストラクタ経由

        Console.WriteLine("---");

        Rectangle r3 = new Rectangle(4);
        // 出力:
        // 幅:4, 高さ:4, 色:黒 の長方形を作成
        // 1引数コンストラクタ経由 (正方形)
    }
}

上記の Rectangle クラスでは、3つのコンストラクタが定義されています。

  • Rectangle(int width, int height) は、
    内部で this(width, height, "白") を呼び出し、3引数のコンストラクタに処理を委譲しています。
  • Rectangle(int sideLength) は、
    内部で this(sideLength, sideLength, "黒") を呼び出しています。

このように this を利用することで、
初期化ロジックの共通部分を一箇所にまとめ、コードの重複を削減し、保守性を高めることができます。

コンストラクタ初期化子 this は、
コンストラクタの本体処理が実行されるに呼び出されることに注意してください。

【C#】コンストラクタとオーバーロード

次に、C#の強力な機能の一つである「オーバーロード」について、
特にコンストラクタとの関連に焦点を当てて解説します。

オーバーロードを理解し使いこなすことで、より柔軟で読みやすいコードを書くことができるようになります。

【C#】オーバーロードとは何ですか?同じ名前で複数の処理を定義するメリット

C#における「オーバーロード(Overload)」とは…

同じ名前のメソッド(またはコンストラクタ)を、引数の型、数、または順番が異なる形で複数定義できる機能
のことです。

メソッドのシグネチャ(メソッド名と引数リストの組み合わせ)が異なれば、
同じ名前のメソッドを複数持つことができます。
戻り値の型だけが異なる場合はオーバーロードとは見なされません。

オーバーロードの主なメリットは以下の通りです。

  1. メソッド名の統一:
    似たような機能を持つメソッドに対して、
    引数の違いによって異なる名前を付ける必要がなくなり、メソッド名を覚えやすくなります。
    例えば、何かを出力する機能に対して、
    出力する対象が整数でも文字列でも Print という名前で統一できます。
  2. 柔軟性の向上:
    メソッドの利用者は、渡したい引数の種類や数に応じて、最適なバージョンのメソッドが自動的に選択されるため、直感的にメソッドを利用できます。
  3. 可読性の向上:
    どのような引数を渡せばよいかが明確になり、コードを読む人が処理内容を理解しやすくなります。
public class Calculator
{
    // int型の2数を加算するメソッド
    public int Add(int a, int b)
    {
        Console.WriteLine("int Add(int, int) が呼ばれました");
        return a + b;
    }

    // double型の2数を加算するメソッド (オーバーロード)
    public double Add(double a, double b)
    {
        Console.WriteLine("double Add(double, double) が呼ばれました");
        return a + b;
    }

    // int型の3数を加算するメソッド (オーバーロード)
    public int Add(int a, int b, int c)
    {
        Console.WriteLine("int Add(int, int, int) が呼ばれました");
        return a + b + c;
    }
}

public class TestCalculator
{
    public static void Main(string[] args)
    {
        Calculator calc = new Calculator();

        Console.WriteLine(calc.Add(10, 20));         // int Add(int, int) が呼ばれる
        Console.WriteLine(calc.Add(3.14, 2.71));     // double Add(double, double) が呼ばれる
        Console.WriteLine(calc.Add(1, 2, 3));        // int Add(int, int, int) が呼ばれる
    }
}

この Calculator クラスでは、
Add という名前のメソッドが3つ定義されていますが、それぞれ引数の型や数が異なっています。
これがオーバーロードです。
呼び出し側は、渡す引数に応じて適切な Add メソッドが自動的に選択されます。

オーバーロードは、
コンストラクタだけでなく、通常のメソッドでも広く利用され、
C#プログラミングにおいて非常に重要なテクニックの一つです。

【C#】コンストラクタのオーバーロード:引数の違いで初期化を柔軟に

コンストラクタもメソッドの一種であるため、当然ながらオーバーロードが可能です。
つまり、1つのクラスに対して、引数の数や型が異なる複数のコンストラクタを定義することができます。

これにより、オブジェクトを生成する際の初期化パターンを複数用意することができ、
クラスの利用者がより便利に、かつ状況に応じて適切な方法でオブジェクトを生成できるようになります。

例えば、User クラスを考えてみましょう。

  • ユーザーIDと名前を指定してユーザーを作成したい場合
  • ユーザーID、名前、メールアドレスを指定してユーザーを作成したい場合
  • デフォルトのゲストユーザーとして作成したい場合(引数なし)

これらの異なる初期化ニーズに対応するために、コンストラクタをオーバーロードします。

public class User
{
    public string UserId { get; private set; }
    public string Name { get; private set; }
    public string Email { get; private set; }

    // コンストラクタ 1: UserIdとNameで初期化
    public User(string userId, string name) : this(userId, name, "N/A") // Emailはデフォルト値
    {
        Console.WriteLine("User(string, string) コンストラクタが呼ばれました。");
    }

    // コンストラクタ 2: UserId, Name, Emailで初期化 (メインのコンストラクタ)
    public User(string userId, string name, string email)
    {
        Console.WriteLine("User(string, string, string) コンストラクタが呼ばれました。");
        if (string.IsNullOrWhiteSpace(userId))
            throw new ArgumentException("UserIdは必須です。", nameof(userId));
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Nameは必須です。", nameof(name));

        UserId = userId;
        Name = name;
        Email = email;
    }

    // コンストラクタ 3: 引数なし (ゲストユーザーとして初期化)
    public User() : this("guest", "Guest User", "guest@example.com")
    {
        Console.WriteLine("User() コンストラクタが呼ばれました。");
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"ID: {UserId}, Name: {Name}, Email: {Email}");
    }
}

public class UserManagement
{
    public static void Main(string[] args)
    {
        User user1 = new User("taro123", "山田太郎");
        user1.DisplayInfo();
        // User(string, string, string) コンストラクタが呼ばれました。
        // User(string, string) コンストラクタが呼ばれました。
        // ID: taro123, Name: 山田太郎, Email: N/A

        Console.WriteLine("---");

        User user2 = new User("hanako456", "鈴木花子", "hanako@example.com");
        user2.DisplayInfo();
        // User(string, string, string) コンストラクタが呼ばれました。
        // ID: hanako456, Name: 鈴木花子, Email: hanako@example.com

        Console.WriteLine("---");

        User guestUser = new User();
        guestUser.DisplayInfo();
        // User(string, string, string) コンストラクタが呼ばれました。
        // User() コンストラクタが呼ばれました。
        // ID: guest, Name: Guest User, Email: guest@example.com
    }
}

この User クラスでは、3つのコンストラクタをオーバーロードしています。
this 初期化子を使って、処理をメインのコンストラクタ(この場合は3引数のもの)に集約している点にも注目してください。
これにより、初期化ロジックの一貫性を保ちつつ、多様な生成方法を提供できています。

コンストラクタのオーバーロードは、
クラス設計の柔軟性を高め、利用者が直感的にオブジェクトを生成できるようにするための重要なテクニックです。

【C#】コンストラクタのオーバーロードと継承:基底クラスと派生クラスの関係

クラスの継承(Inheritance)関係において、
コンストラクタのオーバーロードはどのように機能するのでしょうか。

まず理解しておくべき重要な点は、
派生クラス(子クラス)は基底クラス(親クラス)のコンストラクタを直接「継承」するわけではない
ということです。

しかし、派生クラスのコンストラクタから、基底クラスのコンストラクタを明示的に呼び出すことは可能です。
この際に、基底クラスに複数のオーバーロードされたコンストラクタが存在する場合、
派生クラスはどの基底クラスのコンストラクタを呼び出すかを選択できます。

これは、コンストラクタ初期化子 base キーワードを使って行います。

// 基底クラス
public class Vehicle
{
    public string RegistrationNumber { get; private set; }

    // 基底クラスのコンストラクタ 1
    public Vehicle(string regNum)
    {
        Console.WriteLine("Vehicle(string) コンストラクタが呼ばれました。");
        RegistrationNumber = regNum;
    }

    // 基底クラスのコンストラクタ 2 (オーバーロード)
    public Vehicle()
    {
        Console.WriteLine("Vehicle() コンストラクタが呼ばれました。");
        RegistrationNumber = "未登録";
    }
}

// 派生クラス
public class Car : Vehicle // Vehicleクラスを継承
{
    public string ModelName { get; private set; }

    // 派生クラスのコンストラクタ 1
    // base(regNum) で基底クラスの Vehicle(string) を呼び出す
    public Car(string regNum, string model) : base(regNum)
    {
        Console.WriteLine("Car(string, string) コンストラクタが呼ばれました。");
        ModelName = model;
    }

    // 派生クラスのコンストラクタ 2
    // base() で基底クラスの Vehicle() を呼び出す
    public Car(string model) : base() // もしくは : base("一時登録") のように特定の値を渡すことも可能
    {
        Console.WriteLine("Car(string) コンストラクタが呼ばれました。");
        ModelName = model;
    }

    // 派生クラスのコンストラクタ 3
    // 明示的にbaseを指定しない場合、基底クラスの引数なしコンストラクタ (Vehicle()) が暗黙的に呼ばれる
    // もし基底クラスに引数なしコンストラクタが存在しない場合はコンパイルエラー
    public Car() // : base() と同じ意味
    {
        Console.WriteLine("Car() コンストラクタが呼ばれました。");
        ModelName = "車種不明";
    }
}

public class Showroom
{
    public static void Main(string[] args)
    {
        Console.WriteLine("--- Car 1 ---");
        Car car1 = new Car("XYZ 123", "SedanX");
        Console.WriteLine($"登録番号: {car1.RegistrationNumber}, モデル名: {car1.ModelName}");
        // 出力:
        // --- Car 1 ---
        // Vehicle(string) コンストラクタが呼ばれました。
        // Car(string, string) コンストラクタが呼ばれました。
        // 登録番号: XYZ 123, モデル名: SedanX

        Console.WriteLine("\n--- Car 2 ---");
        Car car2 = new Car("HatchY");
        Console.WriteLine($"登録番号: {car2.RegistrationNumber}, モデル名: {car2.ModelName}");
        // 出力:
        // --- Car 2 ---
        // Vehicle() コンストラクタが呼ばれました。
        // Car(string) コンストラクタが呼ばれました。
        // 登録番号: 未登録, モデル名: HatchY

        Console.WriteLine("\n--- Car 3 ---");
        Car car3 = new Car();
        Console.WriteLine($"登録番号: {car3.RegistrationNumber}, モデル名: {car3.ModelName}");
        // 出力:
        // --- Car 3 ---
        // Vehicle() コンストラクタが呼ばれました。
        // Car() コンストラクタが呼ばれました。
        // 登録番号: 未登録, モデル名: 車種不明
    }
}

この例では、Vehicle クラスに2つのオーバーロードされたコンストラクタがあります。
Car クラスは Vehicle を継承し、
そのコンストラクタ内で base(...) を使って Vehicle クラスの特定のコンストラクタを呼び出しています。

  • Car(string regNum, string model)base(regNum)Vehicle(string) を呼び出します。
  • Car(string model)base()Vehicle() を呼び出します。
  • Car() は明示的な base 呼び出しがないため、暗黙的に Vehicle() が呼び出されます。

継承とコンストラクタのオーバーロードを組み合わせることで、
より複雑で階層的なオブジェクト構造を効率的に、かつ意図通りに初期化することが可能になります。
基底クラスのどのコンストラクタを呼び出すかを派生クラス側で制御できる点がポイントです。

【C#】コンストラクタの継承の順番は?baseキーワードで親クラスのコンストラクタを呼び出す

C#で派生クラスのオブジェクトを生成する際、コンストラクタの呼び出しには明確な順番があります。
これは非常に重要なルールです。

常に、基底クラス(親クラス)のコンストラクタが先に呼び出され、その処理が完了した後に、派生クラス(子クラス)のコンストラクタの本体処理が実行されます。

派生クラスのコンストラクタから特定の基底クラスのコンストラクタを呼び出すには、
前述の通り「コンストラクタ初期化子」の base キーワードを使用します。
base の後ろに () を付け、その中に引数を指定することで、呼び出したい基底クラスのオーバーロードされたコンストラクタを指定できます。

public class Animal
{
    public string Species { get; protected set; }

    public Animal(string species)
    {
        Species = species;
        Console.WriteLine($"Animalコンストラクタ実行: {Species} が生成されようとしています。");
    }
}

public class Dog : Animal
{
    public string Name { get; private set; }

    // base(species) で Animal(string) を呼び出す
    public Dog(string name, string species) : base(species)
    {
        Name = name;
        Console.WriteLine($"Dogコンストラクタ実行: 名前は {Name} です。");
    }
}

public class PetShop
{
    public static void Main(string[] args)
    {
        Dog myDog = new Dog("ポチ", "柴犬");
        // 出力結果:
        // Animalコンストラクタ実行: 柴犬 が生成されようとしています。
        // Dogコンストラクタ実行: 名前は ポチ です。

        Console.WriteLine($"私のペットは {myDog.Species} の {myDog.Name} です。");
        // 私のペットは 柴犬 の ポチ です。
    }
}

この例では、new Dog("ポチ", "柴犬") とすると、
まず Dog コンストラクタの base("柴犬") 部分が評価され、
Animal クラスの Animal("柴犬") コンストラクタが呼び出されます。

Animal コンストラクタの処理が完了した後、
Dog コンストラクタの本体(Name = name; ...)が実行されます。

もし、派生クラスのコンストラクタで base 初期化子を明示的に指定しない場合
コンパイラは自動的に基底クラスの引数なしのコンストラクタ (base()) を呼び出そうとします

このとき、基底クラスに引数なしのコンストラクタが
(明示的に定義されているか、デフォルトコンストラクタとして自動生成される形で)存在しない場合は、
コンパイルエラーとなります。

public class A
{
    // 引数ありコンストラクタのみ定義
    public A(string message)
    {
        Console.WriteLine($"A: {message}");
    }
}

public class B : A
{
    // base() を暗黙的に呼び出そうとするが、Aには引数なしコンストラクタがないためエラー
    /*
    public B() // コンパイルエラー!
    {
        Console.WriteLine("B");
    }
    */

    // 正しくは、Aの引数ありコンストラクタを明示的に呼び出す必要がある
    public B(string msgForA) : base(msgForA)
    {
        Console.WriteLine("B");
    }
}

このコンストラクタの呼び出し順序と base キーワードの適切な使い方を理解することは、
クラスの継承を正しく、そして効果的に扱う上で非常に重要です。
基底クラスの初期化が派生クラスの初期化よりも先に行われることで、
派生クラスは基底クラスのメンバーが既に初期化されていることを前提として自身の初期化処理を進めることができます。

【補足】C#のコンストラクタの返り値はある?実は特殊な存在

C#のコンストラクタは、見た目上、メソッドの一種ではありますが、
通常のメソッドとは異なるいくつかの特徴を持っています。その一つが「返り値(戻り値)の扱い」です。

通常のメソッドでは、
intstring、あるいは void(何も返さない場合)といった返り値の型を明示的に宣言します。
しかし、コンストラクタには返り値の型を記述しませんvoid とすら書かないのです。

では、コンストラクタは何も返さないのでしょうか?実はそうではありません。
コンストラクタの「暗黙の返り値」は、生成されたそのクラスの新しいインスタンス(オブジェクト)そのものです。

MyClass obj = new MyClass(); // new MyClass() がコンストラクタ呼び出し
                             // obj には MyClass の新しいインスタンスへの参照が代入される

new 演算子を使ってコンストラクタを呼び出すと、
その処理の結果として、メモリ上に新しいオブジェクトが作成され、
そのオブジェクトへの参照(メモリ上のアドレスのようなもの)が返されます。

この「新しいインスタンスへの参照」が、コンストラクタの実質的な返り値と考えることができます。

プログラマーが明示的に return 文を書くことはありませんし、
返り値の型を指定することもありませんが、システム内部では新しいオブジェクトが生成され、
それが利用可能になる、という仕組みになっています。

この特殊な性質を理解しておくことで、
new 演算子とコンストラクタがどのように連携してオブジェクトを生み出しているのか、その役割をより深く把握することができます。

【補足】コンストラクタの逆は?デストラクタとの違いを解説

コンストラクタがオブジェクトの「誕生」と「初期設定」を担当するのに対し、
その「逆」の役割、つまりオブジェクトが「消滅」する際に実行される処理を定義するのが「デストラクタ(Destructor)」です。
C#では「ファイナライザ(Finalizer)」とも呼ばれます。

デストラクタの主な目的は、
オブジェクトが使用していたアンマネージドリソース(C#のガベージコレクタの管理外にあるリソース、例えばファイルハンドル、データベース接続、OSレベルのウィンドウハンドルなど)を解放することです。

C#でデストラクタを定義する際の構文は以下の通りです。

  • クラス名の前にチルダ(~)を付けます。
  • 引数を取ることはできません。
  • アクセス修飾子を持つことはできません。
  • 1つのクラスに1つしか定義できません。
public class FileManager
{
    private IntPtr fileHandle; // アンマネージドリソースの例 (IntPtrはポインタやハンドルを扱う型)
    private bool disposed = false;

    public FileManager(string filePath)
    {
        // ここでファイルを開き、fileHandle を取得する処理 (仮)
        Console.WriteLine($"ファイル「{filePath}」を開いています... (仮のハンドル取得)");
        fileHandle = new IntPtr(12345); // 仮のハンドル
    }

    // これがデストラクタ(ファイナライザ)
    ~FileManager()
    {
        Console.WriteLine("FileManagerのデストラクタが呼ばれました。");
        CleanUp(false); // ファイナライザからの呼び出し
    }

    // リソース解放のためのメソッド (IDisposableパターンでよく使われる)
    public void Dispose()
    {
        CleanUp(true);
        GC.SuppressFinalize(this); // ファイナライザを呼び出さないようにする
    }

    protected virtual void CleanUp(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // マネージドリソースの解放 (もしあれば)
                Console.WriteLine("マネージドリソースを解放中...");
            }

            // アンマネージドリソースの解放
            if (fileHandle != IntPtr.Zero)
            {
                Console.WriteLine($"アンマネージドリソース (ハンドル: {fileHandle}) を解放中...");
                // ここで実際に fileHandle を解放するOSのAPIなどを呼び出す
                fileHandle = IntPtr.Zero;
            }
            disposed = true;
        }
    }
}

public class ResourceDemo
{
    public static void Main(string[] args)
    {
        CreateAndReleaseManager();
        Console.WriteLine("CreateAndReleaseManagerメソッドの実行が完了しました。");
        Console.WriteLine("ガベージコレクションを強制実行します (通常は非推奨)。");
        GC.Collect();
        GC.WaitForPendingFinalizers(); // ファイナライザの完了を待つ
        Console.WriteLine("プログラム終了。");
    }

    static void CreateAndReleaseManager()
    {
        FileManager fm = new FileManager("sample.txt");
        // fm.Dispose(); // 本来はここでDisposeを呼ぶべき
        Console.WriteLine("FileManagerオブジェクトはスコープを抜けました。");
        // fm はここでスコープを抜けるが、すぐにデストラクタが呼ばれるとは限らない
    }
}

デストラクタの注意点と IDisposable パターン:

  • 実行タイミングの不確実性:
    デストラクタは、ガベージコレクタ(GC)によってオブジェクトが回収されるタイミングで呼び出されます。
    このタイミングは予測不可能です。そのため、リソースを即座に解放したい場合には不向きです。
  • パフォーマンスへの影響:
    デストラクタを持つオブジェクトは、GCの処理が少し複雑になるため、
    パフォーマンスに若干の影響を与える可能性があります。

これらの理由から、C#ではアンマネージドリソースの解放には、デストラクタだけに頼るのではなく、IDisposable インターフェースを実装し、Dispose メソッドで明示的にリソースを解放するパターンが強く推奨されています。

そして、Dispose メソッドの呼び出しを保証するために using ステートメントを利用します。

public class SafeFileManager : IDisposable
{
    private IntPtr fileHandle;
    private bool disposed = false;

    public SafeFileManager(string filePath)
    {
        Console.WriteLine($"[Safe] ファイル「{filePath}」を開いています...");
        fileHandle = new IntPtr(67890); // 仮のハンドル
    }

    public void DoSomething()
    {
        if (disposed) throw new ObjectDisposedException(nameof(SafeFileManager));
        Console.WriteLine("[Safe] ファイル操作を実行中...");
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // ファイナライザの呼び出しを抑制
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // マネージドリソースの解放 (もしあれば)
                Console.WriteLine("[Safe] マネージドリソースを解放。");
            }

            // アンマネージドリソースの解放
            if (fileHandle != IntPtr.Zero)
            {
                Console.WriteLine($"[Safe] アンマネージドリソース (ハンドル: {fileHandle}) を解放。");
                fileHandle = IntPtr.Zero;
            }
            disposed = true;
        }
    }

    // 念のためファイナライザも実装 (Disposeが呼ばれなかった場合のセーフティネット)
    ~SafeFileManager()
    {
        Console.WriteLine("[Safe] ファイナライザが呼ばれました。");
        Dispose(false);
    }
}

public class SafeResourceDemo
{
    public static void Main(string[] args)
    {
        using (SafeFileManager sfm = new SafeFileManager("important.dat"))
        {
            sfm.DoSomething();
        } // usingブロックを抜ける際に、sfm.Dispose() が自動的に呼び出される

        Console.WriteLine("--- usingブロックの後 ---");
        // ガベージコレクションを強制実行しても、既にDisposeされているのでファイナライザは通常呼ばれない
        // (GC.SuppressFinalize(this) の効果)
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("プログラム終了。");
    }
}

IDisposable パターンは、リソース管理において非常に重要なので、別途学習することをおすすめします。
デストラクタは、この IDisposable パターンを補完する、あるいは最後の砦としての役割を持つと理解しておくと良いでしょう。

参考情報:

【まとめ】C#のコンストラクタとオーバーロードをマスターして開発効率アップ!

この記事では、C#におけるコンストラクタの基本的な役割や書き方、
そしてオーバーロードの概念とその活用方法について、具体的なコード例を交えながら詳しく解説してきました。

コンストラクタは、オブジェクトが生成される際の初期化処理を担当する非常に重要な仕組みです。
引数を持たせたり、this キーワードを使って他のコンストラクタを呼び出したりすることで、
効率的で安全な初期化が実現できます。

また、コンストラクタを記述しない場合のデフォルトコンストラクタの挙動も理解しておくべきポイントです。

一方、オーバーロードは、同じ名前で引数リストの異なるメソッドやコンストラクタを複数定義できる機能です。
これにより、APIの利用者はメソッド名を覚えやすくなり、より直感的にコードを書くことができます。
コンストラクタをオーバーロードすることで、様々な初期化パターンに対応した柔軟なクラス設計が可能になります。

さらに、継承関係におけるコンストラクタの呼び出し順序や、
base キーワードを使った基底クラスコンストラクタの明示的な呼び出しは、
オブジェクト指向プログラミングの根幹をなす重要な知識です。

これらのC#のコンストラクタとオーバーロードに関する知識をしっかりと身につけ、
実際のプログラミングに活かすことで、あなたの書くC#コードはより洗練され、バグが少なく、保守しやすいものになるでしょう。
結果として、開発効率とコードの品質を大きく向上させることができるはずです。ぜひ、これらの概念を積極的に使って、C#プログラミングのスキルをさらに高めていってください。

他にもC#の様々なテクニックの紹介やエンジニアに役立つ情報を発信していますので興味のある方は以下のリンクからどうぞ!

C#やエンジニアに役立つ情報はこちら

安定して収入を上げたいエンジニアはこちら

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

<PR>

目次