オブジェクトを作成するためには、オブジェクトを正しく初期化してやる必要があります。 そのために、オブジェクトの構築のためのコンストラクターと呼ばれる特殊なメソッドが用意されています。 また、同様にオブジェクトの破棄のためのデストラクターと呼ばれるものもあります。
コンストラクターはインスタンスを正しく初期化するための特別なメソッドです。 コンストラクターは以下のように、クラス名と同じ名前のメソッドを書くことで定義できます。
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; }
こういう書き方を変数初期化子(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 クラスのデストラクターが呼ばれました
この例では、デストラクターはプログラムの終了時に呼び出されます(ガベージ コレクションするときに呼ばれます)。 ガベージ コレクションのタイミングは、通常は制御できないので、デストラクターはいつ呼び出されるかわかりません。
このような性質を持っているため、通常、デストラクターはあまり利用されません。 ほぼ、非管理リソースの破棄漏れ防止用です(参考: 「IDisposable インターフェイスの実装」 )。
破棄のタイミングを明示的に制御する必要がある場合 (例えば、何らかの外部リソース(ファイルやプリンタなど)の破棄(ファイルのバッファのフラッシュやプリンタの解放)を行う必要がある場合)、 後述する using ステートメントというものを使った Dispose を行います。
デストラクターという呼び名と、~ 記号を使う構文は C++ の構文を参考にしたものです。 しかし、C++ のデストラクター(変数のスコープを抜けたとき、もしくは、delete 演算子を呼んだタイミングで呼ばれる)とは呼び出されるタイミングが全然違うので注意してください。
C++ では、特定のスコープを抜けた時に確実に呼びたい処理のためにデストラクターを使うことがありますが、 こういう用途には、C# の場合、 using ステートメント を使います。
C# のデストラクターは、動作的にはむしろ、Java の finalize メソッド(ガベージ コレクションに回収された時点で呼ばれる)と同じです。 実際、C# では「デストラクター」と呼んでいるものは、.NET Framework の中間言語(= C#のコンパイル結果)的にはファイナライザーと呼びます。 呼び名の問題だけではなくて、中間言語にコンパイルした結果としては、デストラクターは Finalize という名前のメソッドになっています
前節クラスの問題 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)