目次

概要

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

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

    • 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)と言います。 (別項で説明するbaseと区別してthis初期化子と言うこともあります。)

ちなみに、初期化子とコンストラクターの実行順序は、 変数初期化子 → コンストラクター初期化子 → コンストラクター本体の順になります。 また、変数初期化子は、メンバーの宣言順と同じ順序で呼び出されます。

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;

詳細は「初期化子」で説明します。

コンストラクターの逆操作

詳しくは後々説明していきますが、コンストラクターと逆の操作を行うものが2つあります。

1つは、デストラクター(destructor)です。 プログラムを書く上で、「確保したら必ず後片付けが必要なリソース」と言うものが存在します。 コンストラクターでリソースを確保したら、セットで後片付けを書く場所がデストラクターです。

using System.Buffers;

class Resource
{
    private byte[] _rentalArray;

    // コンストラクターで「借りてくる」
    public Resource() => _rentalArray = ArrayPool<byte>.Shared.Rent(100);

    // 借りたものは返さないといけない。そのために使うのがデストラクター
    ~Resource() => ArrayPool<byte>.Shared.Return(_rentalArray);
}

詳しくは「デストラクター」で説明します。

もう1つは、分解(deconstruct)です。 コンストラクターは複数の値を1つの複合型にまとめる操作でもあります。 この意味でのコンストラクターにあたるのが分解処理です。

class Point
{
    public int X;
    public int Y;

    // 複数の値を組み合わせる
    public Point(int x, int y) => (X, Y) = (x, y);

    // 複数の値にばらす
    public void Deconstruct(out int x, out int y) => (x, y) = (X, Y);
}

static class Program
{
    static void Main()
    {
        // 組み合わせる
        var p = new Point(1, 2);

        // ばらす
        var (x, y) = p;
    }
}

詳しくは「複合型の分解」で説明します。

演習問題

問題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());
  }
}

更新履歴

ブログ