目次

概要

オブジェクトを作成するためには、オブジェクトを正しく初期化してやる必要があります。 そのために、オブジェクトの構築のためのコンストラクターと呼ばれる特殊なメソッドが用意されています。 また、同様にオブジェクトの破棄のためのデストラクターと呼ばれるものもあります。

ポイント
  • コンストラクターで初期化

    • new したときに呼び出される特殊なメソッド。

    • クラス名と同じ名前で定義する。

  • 例えば、class Person { public Person(string name) { ... } ... }

コンストラクター

コンストラクターはインスタンスを正しく初期化するための特別なメソッドです。 コンストラクターは以下のように、クラス名と同じ名前のメソッドを書くことで定義できます。

class SampleClass
{
  // ↓これがコンストラクター
  SampleClass()
  {
    // インスタンスの初期化用のコードを書く
  }
}

他のメソッドと異なり、戻り値の型は書きません(コンストラクターは戻り値を返すことは出来ません)。

例えば、名簿作成のために個人情報を表す Person というクラスを作ったとします。 説明を簡単にするために、この名簿では名前と年齢だけを管理することにします。 そのため、Personnameage という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.namePerson クラス内で定義された 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 を行います。

注意: Finalize

デストラクターという呼び名と、~ 記号を使う構文は C++ の構文を参考にしたものです。 しかし、C++ のデストラクター(変数のスコープを抜けたとき、もしくは、delete 演算子を呼んだタイミングで呼ばれる)とは呼び出されるタイミングが全然違うので注意してください。

C++ では、特定のスコープを抜けた時に確実に呼びたい処理のためにデストラクターを使うことがありますが、 こういう用途には、C# の場合、 「using ステートメント」 を使います。

C# のデストラクターは、動作的にはむしろ、Java の finalize メソッド(ガベージ コレクションに回収された時点で呼ばれる)と同じです。 実際、C# では「デストラクター」と呼んでいるものは、.NET Framework の中間言語(= C#のコンパイル結果)的にはファイナライザーと呼びます。 呼び名の問題だけではなくて、中間言語にコンパイルした結果としては、デストラクターは Finalize という名前のメソッドになっています

演習問題

問題1

前節クラス問題 1Point 構造体および 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)

解答例1

using System;

/// <summary>
/// 2次元の点をあらわす構造体
/// </summary>
struct Point
{
  public double x; // x 座標
  public double y; // y 座標

  /// <summary>
  /// 座標値 (x, y) を与えて初期化。
  /// </summary>
  /// <param name="x">x 座標値</param>
  /// <param name="y">y 座標値</param>
  public Point(double x, double y)
  {
    this.x = x;
    this.y = y;
  }

  public override string ToString()
  {
    return "(" + x + ", " + y + ")";
  }
}

/// <summary>
/// 2次元空間上の三角形をあらわす構造体
/// </summary>
class Triangle
{
  public Point a;
  public Point b;
  public Point c;

  /// <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)
  {
    this.a = a;
    this.b = b;
    this.c = c;
  }

  /// <summary>
  /// 三角形の面積を求める。
  /// </summary>
  /// <returns>面積</returns>
  public double GetArea()
  {
    double abx, aby, acx, acy;
    abx = b.x - a.x;
    aby = b.y - a.y;
    acx = c.x - a.x;
    acy = c.y - a.y;
    return 0.5 * Math.Abs(abx * acy - acx * aby);
  }
}

/// <summary>
/// Class1 の概要の説明です。
/// </summary>
class Class1
{
  static void Main()
  {
    Triangle t = new Triangle(
      new Point(0, 0),
      new Point(3, 4),
      new Point(4, 3));

    Console.Write("{0}\n", t.GetArea());
  }
}

更新履歴

ブログ