コンストラクター
クラスの初期化処理
クラスのフィールドを明示的に初期化しない場合、自動的に既定値で初期化されます。
これを明示的に初期化するには宣言時に初期化子を与える方法がありますが、あまり複雑な処理は書けません。
その場合はコンストラクターで初期化します。
private class SimpleClass
{
//初期化子による初期化
public string Str1 = "abc";
//初期化しない
public string Str2;
//コンストラクター
public SimpleClass()
{
//コンストラクター内で初期化
Str2 = "def";
}
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass();
Console.WriteLine(sc.Str1);
Console.WriteLine(sc.Str2);
Console.ReadLine();
}
abc def
コンストラクターはクラスのインスタンスを生成する直前に一度だけ呼び出される特殊なメソッドです。
インスタンス生成後にコンストラクターを再度呼び出すことはできません。
コンストラクターの実行が終わるとインスタンスが生成されます。
コンストラクターの定義は
- クラス名と同じ名前
- 戻り値を持たない(voidも書かない)
という形式で行います。
そして、基本的にはアクセス修飾子にpublic
を指定します。
これを省略するとprivate
になるので注意してください。
インスタンス生成時のnew SimpleClass();
というのは、SimpleClass
クラスのコンストラクターを呼び出す、という意味になります。
もしコンストラクターがprivate
の場合、外部からコンスラクターが呼び出せなくなるので、インスタンスが生成できなくなります。
(あえてインスタンスを生成できないクラスを作る場合もあります)
readonlyフィールドも初期化可能
コンストラクター内ではreadonly
フィールドも初期化することができます。
private class SimpleClass
{
readonly float tax;
public SimpleClass()
{
//readonlyフィールドも初期化可能
tax = 1.08f;
}
void TestMethod()
{
//コンストラクター以外からの書き換えはエラー
//tax = 1.08f;
}
}
既定のコンストラクター
クラスにコンストラクターを定義しない場合、自動的に「何もしないpublicなコンストラクター」が生成されます。
そのためコンストラクターを定義していないクラスでもインスタンスが生成できます。
これを既定のコンストラクター、デフォルトコンストラクターといいます。
private class SimpleClass
{
public string Str1;
}
//↑は↓と同じ意味
private class SimpleClass
{
public string Str1;
public SimpleClass() {}
}
フィールドを初期化子で初期化した場合はコンストラクター内で初期化したものとみなされます。
private class SimpleClass
{
public string Str = "abc";
}
//↓同じ意味
private class SimpleClass
{
public string Str;
public SimpleClass()
{
Str = "abc";
}
}
引数付きコンストラクター
コンストラクターはメソッドの一種なので引数を指定することができます。
この引数によって初期化する値を変えることができます。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
public void Speak()
{
Console.WriteLine("私は{1} {0}です。", firstName, lastName);
}
}
static void Main(string[] args)
{
SimpleClass sc1 = new SimpleClass("A山", "B太");
SimpleClass sc2 = new SimpleClass("C谷", "D子");
sc1.Speak();
sc2.Speak();
Console.ReadLine();
}
私はA山 B太です。 私はC谷 D子です。
デフォルトコンストラクターが生成されないことに注意
先ほどのクラスは「引数付きのコンストラクター」のみが定義されているので、引数なしで呼び出せるコンストラクターが存在しません。
コンストラクターがひとつでも定義されている場合はデフォルトコンストラクターは自動生成されません。
つまり、このクラスは引数なしではインスタンスが生成できないということになります。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
}
static void Main(string[] args)
{
//エラー
SimpleClass sc = new SimpleClass();
}
引数なしでもインスタンスを生成したい場合はコンストラクターをオーバーロードして引数なしのコンストラクターを定義します。
コンストラクターの中身は空でも構いませんし、何かの値でフィールドを初期化しても構いません。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass()
{
firstName = string.Empty;
lastName = string.Empty;
}
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
}
もしくはデフォルト引数を使用して、引数なしでもコンストラクターを呼び出せるようにします。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass(string lastName = "", string firstName = "")
{
this.firstName = firstName;
this.lastName = lastName;
}
}
オブジェクト初期化子
コンストラクターとは直接関係ないのですが、クラスのメンバーを初期化するにはもうひとつ方法があります。
クラス内のpublicメンバーに対してはインスタンス生成時にコンストラクターを介さずに初期化することができます。
class SimpleClass
{
public string FirstName;
public string LastName;
public SimpleClass()
{
FirstName = string.Empty;
LastName = string.Empty;
}
}
static void Main(string[] args)
{
//オブジェクト初期化子で初期化
SimpleClass sc = new SimpleClass() //←ここにセミコロンがないことに注意
{
LastName = "A山",
FirstName = "B太"
};
SimpleClass sc2 = new SimpleClass //()を省略することもできる
{
LastName = "A山",
FirstName = "B太"
};
}
インタンスの生成に続いてブロック{}
を書き、その中にクラスのメンバーに値を代入することで初期化できます。
これをオブジェクト初期化子といいます。
これは以下のコードと同じ意味になります。
SimpleClass sc = new SimpleClass();
sc.LastName = "A山";
sc.FirstName = "B太";
必要ならばコンストラクターと組み合わせることもできます。
class SimpleClass
{
public string FirstName;
public string LastName;
public SimpleClass(string lastName = "", string firstName = "")
{
FirstName = firstName;
LastName = lastName;
}
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass("A山")
{
FirstName = "B太"
};
}
オブジェクト初期化子はクラスの外部からのアクセスになるので、コンストラクターとは違いpublicなメンバーしか初期化できません。
(その場所からアクセスできるならprivateやprotectedメンバーに対しても可能)
また、コンストラクターではないので読み取り専用プロパティやreadonly
なフィールドを初期化することもできません。
class SimpleClass
{
private int A;
public int B { get; }
readonly public int C;
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass()
{
A = 1, //エラー
B = 2, //エラー
C = 3 //エラー
};
}
initアクセサ
読み取り専用プロパティ(getのみのプロパティ)はオブジェクト初期化子では初期化できませんが、C#9.0以降ではinitアクセサを使用することでオブジェクト初期化子で初期化可能な読み取り専用プロパティを定義することができます。
class SimpleClass
{
public string FirstName { get; set; }
public string LastName { get; init; } //initアクセサ
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass()
{
LastName = "A山", //OK
FirstName = "B太"
};
//コンパイルエラー
//sc.LastName = "C村";
}
initアクセサはコンスタクターおよびオブジェクト初期化子内でのみ初期化が可能なプロパティです。
それ以外の場所では最初に代入が行われる場合でもコンパイルエラーになります。
//LastNameはinitアクセサ
SimpleClass sc = new SimpleClass();
sc.LastName = "A山"; //コンパイルエラー
sc.FirstName = "B太";
コピーコンストラクター
コピーコンストラクターというのは、自分のクラスのインスタンスのコピーを作るコンストラクターのことです。
C#にはコピーコンストラクターの機能は標準では提供されていないので、自分で定義する必要があります。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass()
{
firstName = string.Empty;
lastName = string.Empty;
}
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
//コピーコンストラクター
public SimpleClass(SimpleClass sc)
{
firstName = sc.firstName;
lastName = sc.lastName;
}
}
static void Main(string[] args)
{
SimpleClass sc1 = new SimpleClass("A山", "B太");
//sc1のコピーを得る
SimpleClass sc2 = new SimpleClass(sc1);
}
コピーコンストラクターは自クラスのインスタンスを引数で受け取ります。
コンストラクター内でフィールドをすべてコピーすれば、インスタンスのコピーが作られます。
ただし参照型のフィールドがある場合はただの代入ではコピーできないので注意が必要です。
コンストラクター初期化子
コンストラクターは別のコンストラクターを呼び出すことができます。
private class SimpleClass
{
string firstName;
string lastName;
public SimpleClass()
: this("", "") { }
//↑は↓のコンストラクターを呼ぶ
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
//↓は↑のコンストラクターを呼ぶ
public SimpleClass(SimpleClass sc)
: this(sc.lastName, sc.firstName) { }
}
コンストラクターの引数に続いて: this (引数リスト)
と書くと、指定の引数リストを持つ別のコンストラクターを呼び出せます。
これをコンストラクター初期化子といいます。
サンプルコードでは、引数なしでコンストラクターを呼び出すとフィールドが空文字で初期化され、自クラスのインスタンスを引数にしてコンストラクターを呼び出すとコピーコンストラクターの働きとなります。
このように、ほとんど同じ処理をするコンストラクターをまとめてしまうことでコードが短くなり、修正が容易になります。
別のコンストラクターを呼び出した後に元のコンストラクター内を実行することもできます。
private class SimpleClass
{
string firstName;
string lastName;
int num;
public SimpleClass()
: this("", "")
{
num = 1;
}
//↑は↓のコンストラクターを呼ぶ
public SimpleClass(string lastName, string firstName)
{
this.firstName = firstName;
this.lastName = lastName;
}
//↓は↑のコンストラクターを呼ぶ
public SimpleClass(SimpleClass sc)
: this(sc.lastName, sc.firstName)
{
num = 2;
}
}
このコードのフィールドnum
は、引数なしコンストラクターでは1で初期化され、コピーコンストラクターでは2で初期化されます。
引数に文字列を指定してインスタンスを生成した場合は明示的な初期化がないので、既定値である0で初期化されます。
なお、コンストラクターの呼び出し順は「コンストラクター初期化子→元のコンストラクター」の順です。
そのため、真ん中のコンストラクターでnum
を初期化していても、呼び出し元のコンストラクター内で別の初期化処理がある場合はそちらの値となります。
ファイナライザー(デストラクター)
クラスの初期化処理にコンストラクターがあるように、クラスの破棄時の処理にファイナライザー(デストラクター)という処理があります。
C#でのクラスの破棄の大部分はファイナライザーでは行いません。
詳しくはオブジェクトの破棄の項で説明します。