C#のコンストラクタとは?Studentクラスをnewと同時に初期化しよう

広告

前回までのチュートリアルで、みなさんはすでにこのStudentクラスを書いて、実行して、結果を確認していると思います。

using System;
namespace StudentClassTutorial
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student();
            student1.Name = "田中";
            student1.Score = 80;
            Console.WriteLine($"{student1.Name}さんの点数は{student1.Score}点です。");
        }
    }
    class Student
    {
        public string Name;
        public int Score;
    }
}

new Student(); でオブジェクトを作ったあと、student1.Name = "田中"; student1.Score = 80; と、1行ずつフィールドに値を設定していく書き方でしたね。動かせば、ちゃんと「田中さんの点数は80点です。」と表示されたはずです。

このコード、動くには動きます。ですが、「オブジェクトを作る」ことと「値を設定する」ことが別々の行に分かれているせいで、実はちょっとした落とし穴があります。今回はこのコードを起点に、「コンストラクタ」という仕組みを学んでいきましょう。

1. このコードの弱点 — 分かれているから、忘れられる

Student student1 = new Student(); の時点では、NameScoreも空っぽ(文字列はnull、点数は0)のままです。その後に続く2行を書いて、はじめて中身が入ります。

つまり、次のようなコードも普通に書けてしまいます。

Student student2 = new Student();
student2.Name = "佐藤";
// Scoreを書き忘れた!

student2.Score = ...; の行を書き忘れても、コンパイルエラーにはなりません。実行もできてしまいます。結果として、student2.Scoreは初期値の0のまま、誰にも気づかれずに残ってしまいます。

コンストラクタを使うと何が変わるかを示すBefore/After比較図

3行に分けて設定する書き方は、裏を返せば「1行だけ書き忘れても、コンパイラは教えてくれない」ということです。オブジェクトが生まれた瞬間から、実はまだ「作りかけ」の状態でも、コードの上では何の異常もなく動き続けてしまうのです。

2. 解決策 — コンストラクタで「生まれると同時に」初期化する

そこで登場するのが「コンストラクタ」です。コンストラクタとは、クラスと同じ名前を持つ、戻り値のない特別なメソッドで、newされた瞬間に自動的に呼び出されます。

コンストラクタに引数を用意しておけば、「オブジェクトを作る」と「必要な値を渡す」を、同時に・1行で行えるようになります。しかも、コンストラクタが引数を要求していれば、呼び出す側は必ずその引数を渡さなければならず、渡さなければそもそもコンパイルが通りません。「書き忘れ」自体が、構造的に起こらなくなるわけです。

3. 実際に書き換えてみよう

それでは、Studentクラスにコンストラクタを追加してみましょう。

コンストラクタを追加したStudentクラスのコード解説図

ポイントは次の4つです。

  1. public Student(string name, int score) — クラスと同じ名前のメソッド。戻り値は書かず、newされた瞬間に自動的に呼び出される
  2. Name = name; — 渡された引数nameを、その場でフィールドNameに代入する
  3. Score = score; — 同じように、渡されたscoreをフィールドScoreに代入する
  4. new Student("田中", 80);newと同時に引数を渡すだけで、初期化までまとめて完了する

コード全体は次のようになります。

using System;
namespace StudentClassTutorial
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Student student1 = new Student("田中", 80);
            Console.WriteLine($"{student1.Name}さんの点数は{student1.Score}点です。");
        }
    }
    class Student
    {
        public string Name;
        public int Score;

        public Student(string name, int score)
        {
            Name = name;
            Score = score;
        }
    }
}

注目してほしいのは、Mainメソッド側がぐっとシンプルになった点です。書き換え前は3行に分かれていた

Student student1 = new Student();
student1.Name = "田中";
student1.Score = 80;

が、書き換え後はたった1行、

Student student1 = new Student("田中", 80);

で済んでしまいます。しかも、new Student()のように引数を省略して呼び出そうとすると、今度はコンパイルエラーになります。「名前と点数、両方そろっていないと、そもそもStudentは作れない」というルールを、コンストラクタがクラス自身の力で守ってくれるようになったのです。

4. まとめ:コンストラクタで得られる4つのメリット

Student student1 = new Student();と1行ずつフィールドに代入していく書き方を、コンストラクタに置き換えるだけで、クラスの安心感は大きく変わります。

コンストラクタで得られる4つのメリットのまとめ図
  • その場で初期化:newすると同時に、必要な値がすべてそろった状態でオブジェクトが生まれる
  • 書き忘れを防げる:引数を渡さなければコンパイルが通らないので、値の設定漏れがそもそも起こらない
  • コードがすっきり:3行に分かれていた初期化処理が、1行にまとまる
  • 複数生成も安全:student1student2student3……と何個newしても、同じルールで必ず初期化される

おわりに

「1行ずつフィールドに代入する」から「コンストラクタで、生まれると同時に初期化する」への書き換え— これだけのことですが、「作りかけのオブジェクトが生まれてしまう」という危うさを、根本からなくすことができます。

前回学んだprivate+プロパティによる「カプセル化」と組み合わせれば、コンストラクタでしっかり初期化された上で、その後の値の変更も安全にチェックできる、より頑丈なStudentクラスに育てていくこともできます。ぜひみなさんのStudentクラスにもコンストラクタを追加して、new Student();と引数なしで呼び出そうとするとどうなるか、実際に試してみてください。

訪問数 4 回, 今日の訪問数 4回

広告

C#

Posted by hidepon