オブジェクトを作成するためには、オブジェクトを正しく初期化してやる必要があります。 そのために、オブジェクトの構築のためのコンストラクターと呼ばれる特殊なメソッドが用意されています。 また、同様にオブジェクトの破棄のためのデストラクターと呼ばれるものもあります。
コンストラクターはインスタンスを正しく初期化するための特別なメソッドです。 コンストラクターは以下のように、クラス名と同じ名前のメソッドを書くことで定義できます。
class SampleClass { // ↓これがコンストラクター SampleClass() { // インスタンスの初期化用のコードを書く } }
他のメソッドと異なり、戻り値の型は書きません(コンストラクターは戻り値を返すことは出来ません)。
例えば、名簿作成のために個人情報を表す Person
というクラスを作ったとします。
説明を簡単にするために、この名簿では名前と年齢だけを管理することにします。
そのため、Person
は name
と age
という2つのメンバーのみを定義します。
class Person { public string name; // 名前 public int age; // 年齢 }
ここで、Person
クラスのインスタンスを生成する際、
名前を ""
(空の文字列)で、年齢を 0
で初期化したいとします。
そのためには以下のようなコンストラクターを作成します。
class Person { public string name; // 名前 public int age; // 年齢 // ↓これが Person クラスのコンストラクター public Person() { name = ""; age = 0; } }
コンストラクターは new
を用いてインスタンスを作成する際に呼び出されます。
例えば、下記のようなコードを実行した場合、
using System; class Test { public Test() { Console.Write("Test クラスのコンストラクターが呼ばれました\n"); } } class ConstructorSample { static void Main() { Console.Write("Main の先頭\n"); Test t = new Test(); // ここで Test のコンストラクターが呼ばれる Console.Write("Main の末尾\n"); } }
以下のような出力が得られます。
Main の先頭 Sample クラスのコンストラクターが呼ばれました Main の末尾
また、コンストラクターには引数を与えることもできます。
例えば、先ほどの Person
クラスで、
インスタンスの作成時に名前と年齢の値を設定したい場合、
以下のようなコンストラクターを作成します。
class Person { public string name; // 名前 public int age; // 年齢 // ↓引数つきの Person クラスのコンストラクター public Person(string name, int age) { this.name = name; this.age = age; } }
この例で使われている
this
というキーワードは、
作成するインスタンス自身を格納する特別な変数です。
そのため、この例では this.name
は Person
クラス内で定義された name
のことになります。
一方、this
の付いていない方の name
は、コンストラクターの引数として定義した name
のことです。
引数つきのコンストラクターを呼び出すためには、new
を使ってインスタンスを生成する際に、以下のようにして引数を渡します。
クラス名 変数名 = new クラス名(引数リスト);
例えば、先ほど定義したPerson
クラスのコンストラクターを呼び出すためには以下のようにします。
Person p = new Person("ビスケット・クルーガー", 57); Console.Write(p.age); // 57 と表示される
また、コンストラクターはオーバーロードすることができます。
例えば、Person
クラスに、名前と年齢を引数として与えるコンストラクターと、何も引数を与えないコンストラクターの両方を定義することができます。
class Person { public string name; // 名前 public int age; // 年齢 // ↓引数なしの Person クラスのコンストラクター public Person() { this.name = ""; this.age = 0; } // ↓引数つきの Person クラスのコンストラクター public Person(string name, int age) { this.name = name; this.age = age; } }
using System; /// <summary> /// 名簿用の個人情報記録用のクラス。 /// とりあえず、名前と年齢のみ。 /// </summary> class Person { // public なメンバー変数 public string name; // 氏名 public int age; // 年齢 // 定数 const int UNKNOWN = -1; const string DEFAULT_NAME = "デフォルトの名無しさん"; /// <summary> /// 名前と年齢を初期化 /// 与えられた年齢が負のときは年齢不詳とみなす /// </summary> /// <param name="name">氏名</param> /// <param name="age">年齢</param> public Person(string name, int age) { this.name = name; this.age = age > 0 ? age : UNKNOWN; } /// <summary> /// 名前のみを初期化 /// 年齢は不詳とする /// </summary> /// <param name="name">氏名</param> public Person(string name) : this(name, UNKNOWN) { } /// <summary> /// デフォルトコンストラクター /// 氏名・年齢ともに不詳 /// </summary> public Person() : this(null, UNKNOWN) { } /// <summary> /// 文字列化 /// 氏名が不詳のときには NONAME に設定された名前を返す /// 年齢が不詳の時には名前のみを返す /// 氏名・年齢が分かっているときには「名前(xx歳)」という形の文字列を返す /// </summary> public override string ToString() { if(name == null) return DEFAULT_NAME; if(age == UNKNOWN) return name; return name + "(" + age + "歳)"; } }//class Person //---------------------------------------------------- // メインプログラム class ConstructorSample { static void Main() { Person p1 = new Person("ちゆ", 12); Person p2 = new Person("澪"); Person p3 = new Person(); Console.Write("{0}\n{1}\n{2}\n", p1, p2, p3); } }
ちゆ(12歳) 澪 デフォルトの名無しさん
メンバー変数に初期値を与えるだけなら、 コンストラクターを使わなくても、以下の様な書き方で初期化できます。
class Person { public string name = ""; public int age = 0; }
こういう書き方をメンバー変数初期化子(member variable initializer)と言います。
また、以下のような書き方で、別のコンストラクターを呼び出すことができます。
class Person { public string name; public int age; public Person() : this("", 0) // ↓のPerson(string, int) が呼ばれる。 { } public Person(string name, int age) { this.name = name; this.age = age; } }
この書き方をコンストラクター初期化子(constructor initializer)と言います。
ちなみに、初期化子とコンストラクターの実行順序は、 メンバー変数初期化子 → コンストラクター初期化子 → コンストラクター本体の順になります。 また、メンバー変数初期化子は、メンバーの宣言順と同じ順序で呼び出されます。
using System; class Member { public Member(int n) { Console.Write("メンバー変数初期化子 {0}\n", n); } } class Test { Member x = new Member(1); Member y = new Member(2); public Test() : this(0) { Console.Write("引数なしのコンストラクター\n"); } public Test(int x) { Console.Write("引数 x = {0} 付きのコンストラクター\n", x); } } class Program { static void Main() { new Test(); } }
メンバー変数初期化子 1 メンバー変数初期化子 2 引数 x = 0 付きのコンストラクター 引数なしのコンストラクター
Ver. 3.0
C# 3.0 から、以下のような記法でメンバーを初期化できるようになりました。
Point p = new Point{ X = 0, Y = 1 };
ちなみに、このコードの実行結果は以下のようなコードと等価です。
Point p = new Point();
p.X = 0;
p.Y = 1;
詳細は 「初期化子」 で説明します。
コンストラクターとは逆に、インスタンスが破棄されるときに呼び出されるのがデストラクターです。
デストラクターは以下のように、クラス名の前に ~
を付けた名前のメソッドを書くことで定義できます。
class SampleClass { // ↓これがデストラクター ~SampleClass() { // インスタンスの破棄用のコードを書く } }
デストラクターはコンストラクターと違って、引数を持つことができません。
.NET Framework では、インスタンスの寿命は .NET Framework 自体が管理していて、 いつインスタンスの破棄が行われるのかは分かりません。 (C++ 言語に慣れている人は注意が必要。)
using System; class Test { public Test() { Console.Write("Test クラスのコンストラクターが呼ばれました\n"); } ~Test() { Console.Write("Test クラスのデストラクターが呼ばれました\n"); } } class DestructorSample { static void Main() { Console.Write("1\n"); Test t = new Test(); // ここで Test のコンストラクターが呼ばれる Console.Write("2\n"); t = null; // ↑で作成したインスタンスはもう利用されなくなる // でも、デストラクターはまだ呼ばれない Console.Write("3\n"); } }
1 Test クラスのコンストラクターが呼ばれました 2 3 Test クラスのデストラクターが呼ばれました
この例では、デストラクターはプログラムの終了時にちゃんと呼び出されていますが、 呼び出されない場合もあります。 このような性質を持っているため、通常、デストラクターはあまり利用されません。
破棄のタイミングを明示的に制御する必要がある場合 (例えば、何らかの外部リソース(ファイルやプリンタなど)の破棄(ファイルのバッファのフラッシュやプリンタの解放)を行う必要がある場合)、 後述する using 文というものを使った Dispose を行います。
前節クラスの問題 1の Point
構造体および Triangle
クラスに、
以下のようなコンストラクターを追加せよ。
/// <summary> /// 座標値 (x, y) を与えて初期化。 /// </summary> /// <param name="x">x 座標値</param> /// <param name="y">y 座標値</param> public Point(double x, double y)
/// <summary> /// 3つの頂点の座標を与えて初期化。 /// </summary> /// <param name="a">頂点A</param> /// <param name="b">頂点B</param> /// <param name="c">頂点C</param> public Triangle(Point a, Point b, Point c)