応用編

応用編1日目:コンストラクタ・デストラクタ

インスタンスの生成・破棄時に呼ばれる特殊なメソッドと、メモリ管理の仕組みを学びましょう

応用編: 1 2 3 4 5 6 7

コンストラクタ

サンプルプログラム

C# のクラスには、インスタンスを生成する際にただ一度だけ呼び出される、コンストラクタと呼ばれる特殊なメソッドが存在します。以下のサンプルを入力・実行してみてください。

プロジェクト: SampleEx101 / ファイル名: Person.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx101
{
    class Person
    {
        //  フィールド name(名前)
        private string name = "";
        //  フィールド age(年齢)
        private int age = 0;
        //  コンストラクタ(引数なし)
        public Person() : this("名無し", 0)
        {
            Console.WriteLine("引数なしコンストラクタ");
        }
        //  コンストラクタ(引数あり)
        public Person(string name, int age)
        {
            this.name = name;
            this.age  = age;
            Console.WriteLine("引数ありコンストラクタ name:{0} age:{1}", name, age);
        }
        //  情報の表示
        public void ShowAgeAndName()
        {
            Console.WriteLine("名前:{0} 年齢:{1}", name, age);
        }
        //  name のプロパティ
        public string Name
        {
            set { name = value; }
            get { return name;  }
        }
        //  age のプロパティ
        public int Age
        {
            set { age = value; }
            get { return age;  }
        }
    }
}
プロジェクト: SampleEx101 / ファイル名: Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx101
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p1, p2;
            p1 = new Person();             //  引数なしのコンストラクタ
            p2 = new Person("太田隆", 29); //  引数ありのコンストラクタ
            p1.Name = "斉藤花子";
            p1.Age  = 18;
            p1.ShowAgeAndName();
            p2.ShowAgeAndName();
        }
    }
}
実行結果
引数ありコンストラクタ name:名無し age:0
引数なしコンストラクタ
引数ありコンストラクタ name:太田隆 age:29
名前:斉藤花子 年齢:18
名前:太田隆 年齢:29

コンストラクタとは

Person.cs の16行目および21行目を見てください。ここに、戻り値がなく、クラス名と同じ名前のメソッドが定義されています。これがコンストラクタです。コンストラクタはインスタンスが生成されるときに一度だけ呼び出されるメソッドで、インスタンス生成時の初期化処理などをここに記述します。

コンストラクタはこのように一つのクラスで複数定義できます。どのコンストラクタが呼ばれるかは、生成時に与えられた引数に依存します。Program.cs の14行目のように引数がない場合は Person.cs の16行目が、15行目のように引数がある場合は Person.cs の21行目が呼び出されます。

Person クラスのコンストラクタとインスタンス生成の対応関係
インスタンス生成呼び出されるコンストラクタ
p1 = new Person();Person()(引数なし)
p2 = new Person("太田隆", 29);Person(string name, int age)(引数あり)
⚠️ インスタンス生成時に与えた引数に対応するコンストラクタが存在しない場合はエラーになります。

コンストラクタの this による連鎖呼び出し

Person.cs の16行目にある、: の後の this は何を意味するのでしょうか?これは、この引数なしコンストラクタを実行する前に、21行目から定義されている引数付きのコンストラクタを呼び出すという意味です。

第1引数(name)には「名無し」が、第2引数(age)には 0 がそれぞれ与えられます。そのため、引数なしコンストラクタを呼び出すと、まず「引数ありコンストラクタ name:名無し age:0」と表示されてから、「引数なしコンストラクタ」と表示されます(図1-1)。

this により引数付きコンストラクタを呼び出す。番号は実行順

図1-1. this により、引数付きコンストラクタを先に呼び出す(番号は実行順)

ガーベージコレクション

サンプルプログラム

プログラムの中でインスタンスを大量に生成していくと、その分メモリを圧迫していきます。しかし、すべてのインスタンスが常に必要というわけではありません。C# では、プログラマーが特定のインスタンスを直接消去することはできませんが、代わりにガーベージコレクションという仕組みが存在し、不要なオブジェクトを自動的に整理してくれます。以下のプログラムはガーベージコレクタの使用例です。

プロジェクト: SampleEx102 / ファイル名: Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx102
{
    class Program
    {
        static void Main(string[] args)
        {
            String[] a = new String[10000];
            for (int i = 0; i < 10000; i++)
            {
                a[i] = new String('M', 10000);
            }
            Console.WriteLine("メモリ使用量(GC発動前) :" + GC.GetTotalMemory(false));
            //  a の参照を解放
            a = null;
            Console.WriteLine("メモリ使用量(参照解除後):" + GC.GetTotalMemory(false));
            GC.Collect();
            Console.WriteLine("メモリ使用量(GC発動後) :" + GC.GetTotalMemory(false));
        }
    }
}
実行結果(例)
メモリ使用量(GC発動前) :200223340
メモリ使用量(参照解除後):200239724
メモリ使用量(GC発動後) :31312

String クラス

ガーベージコレクタについて説明する前に、13行目で使われている String クラスについて説明します。このクラスはすでに使っている string とまったく同じものです。

String クラスは文字列データの保持および操作を行うためのクラスで、文字列の分割・結合・比較・検索などのメソッドを持ちます。一般に、文字列データを定義する際には小文字の string、クラスとしてメソッドを呼び出す際には String と使い分けられますが、基本的に両者は同じものです。

このサンプルの16行目では以下のような処理を行っています。

String クラスのインスタンスの生成
a[i] = new String('M', 10000);

ここでは、文字 'M' を10000個連ねた文字列を作っています。さらに、配列変数 a は String 型の10000個の要素を持つ配列変数です。13〜17行目の処理により、長さ10000の文字列が10000個作成されるため、メモリ使用量はかなり大きくなります。しかし、ガーベージコレクションを発動させることでメモリ使用量が一気に下がったことがわかります。

ガーベージコレクションの役割と起動

ガーベージとは英語で「ゴミ」を意味します。つまり、ガーベージコレクションとはメモリの「ゴミ収集」のことです。C# の実行環境では、ガーベージコレクションが自動的に働いてメモリの清掃をしてくれます(図1-2)。

ガーベージコレクション

図1-2. ガーベージコレクションのイメージ

ガーベージコレクションがどこで発動されるかはプログラマーには分かりません。しかし、以下の処理でユーザー側から強制的に起動させることもできます(Program.cs 22行目)。

ガーベージコレクションの強制起動
GC.Collect();

メモリの解放

ガーベージコレクションは、どのようにして使用中のメモリとそうでないメモリを区別するのでしょうか?このプログラムのポイントは20行目にあります。

参照に null を代入
a = null;

null とは「何もない」ことを意味します。クラスへの参照を持つ変数に null を代入することで、その変数がいかなるオブジェクトも参照していない状態になります。これにより、13行目で生成された String 型の配列は外部からの参照がなくなり、メモリ内に存在するものの使用されない状態となります。ガーベージコレクションはこのようなデータを自動的に消去します。

メモリ使用量の取得

現在使用されているメモリ量は、以下の処理で調べることができます。

メモリ使用量の取得
GC.GetTotalMemory(false);

プログラム内では18行目・21行目・23行目でこの処理を行っています。18行目と21行目の結果はほぼ変わりませんが、23行目(ガーベージコレクション後)で値が極端に減少していることが確認できます。

ジェネレーション

new によってインスタンスが生成されるメモリ領域のことをヒープと言います。ヒープに生成されるオブジェクトは、その生存期間に応じて0〜2の3種類に分類されます。この分類をジェネレーションと言います。特定のジェネレーションに対してのみガーベージコレクションを発動させることもできます。

特定のジェネレーションのメモリを消去
GC.Collect(ジェネレーションの数);

デストラクタ

サンプルプログラム

最後に、コンストラクタとは反対の概念であるデストラクタについて紹介します。デストラクタとは、オブジェクトが破棄されるときに呼び出される特殊なメソッドです。以下のサンプルを実行してみてください。

プロジェクト: SampleEx103 / ファイル名: Dummy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx103
{
    class Dummy
    {
        //  コンストラクタ
        public Dummy()
        {
            Console.WriteLine("コンストラクタ");
        }
        //  デストラクタ
        ~Dummy()
        {
            Console.WriteLine("デストラクタ");
        }
    }
}
プロジェクト: SampleEx103 / ファイル名: Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleEx103
{
    class Program
    {
        static void Main(string[] args)
        {
            Dummy d = new Dummy();
        }
    }
}
実行結果
コンストラクタ
デストラクタ

デストラクタの定義

コンストラクタがクラス名と同じ名前だったのに対し、デストラクタはクラス名の先頭に ~(チルダ)が付きます。

デストラクタの定義
~(クラス名)()

したがって、Dummy クラスのデストラクタは Dummy.cs の17行目のようになります。

Dummy クラスのデストラクタ
~Dummy()

コンストラクタとの違いとして、デストラクタは引数を持ちません。また、アクセス修飾子は省略しなければなりません。

デストラクタの呼び出し

デストラクタはオブジェクトが破棄されるときに呼び出されます。コンストラクタと異なり、明示的に呼び出すことはできません。このサンプルでは、Program.cs の Main() メソッドの終了、つまりプログラムが終わるときに呼び出されています。また、プログラム実行中にガーベージコレクションによってオブジェクトが消去される際にも呼び出されます。

💡 C# では、ガーベージコレクションが自動的にメモリを管理するため、デストラクタを明示的に実装する機会は多くありません。リソースの解放には IDisposable インターフェースと using ステートメントを使うことが推奨されています。

練習問題(応用編)1 へ →