コンストラクターのデザイン
コンストラクターは、型を初期化し、型のインスタンスを作成するために使用する特別なメソッドです。 型コンストラクターは、型に含まれる静的データの初期化に使用します。 共通言語ランタイム (CLR: Common Language Runtime) は、型のインスタンスを作成する前に型コンストラクターを呼び出します。 型コンストラクターは static (Visual Basic では Shared) であり、パラメーターを受け取ることができません。 インスタンス コンストラクターは、型のインスタンスの作成に使用します。 インスタンス コンストラクターの場合は、パラメーターを受け取ることができますが、必須ではありません。 パラメーターなしのインスタンス コンストラクターは、既定のコンストラクターと呼ばれます。
次のガイドラインでは、コンストラクターの作成に関する推奨事項を示します。
簡素なコンストラクター (理想的には既定のコンストラクター) を提供するようにしてください。 簡素なコンストラクターとは、パラメーターの数が非常に少なく、すべてのパラメーターがプリミティブ型または列挙型であるパラメーターです。
目的とする操作のセマンティクスが、新しいインスタンスの構築に直接対応しない場合、またはコンストラクターのデザイン ガイドラインに従うのが不自然に思われる場合は、コンストラクターではなく、静的ファクトリ メソッドを使用するようにしてください。
コンストラクターのパラメーターは、メイン プロパティを設定するためのショートカットとして使用してください。
コンストラクターを使用してプロパティを設定しても、プロパティを直接設定しても同じ結果になる必要があります。 次のコード例に示す EmployeeRecord クラスは、コンストラクターを呼び出しても、プロパティを直接設定しても初期化できます。 EmployeeManagerConstructor クラスは、コンストラクターを使用した EmployeeRecord オブジェクトの初期化を示しています。 EmployeeManagerProperties クラスは、プロパティを使用した EmployeeRecord オブジェクトの初期化を示しています。 Tester クラスは、使用した手法とは関係なく、オブジェクトが同じ状態になっていることを示しています。
Imports System
Imports System.Collections.ObjectModel
namespace Examples.DesignGuidelines.Constructors
' This Class can get its data either by setting
' properties or by passing the data to its constructor.
Public Class EmployeeRecord
private employeeIdValue as Integer
private departmentValue as Integer
Public Sub New()
End Sub
Public Sub New(id as Integer, department as Integer)
Me.employeeIdValue = id
Me.departmentValue = department
End Sub
Public Property Department as Integer
Get
Return departmentValue
End Get
Set
departmentValue = value
End Set
End Property
Public Property EmployeeId as Integer
Get
Return employeeIdValue
End Get
Set
employeeIdValue = value
End Set
End Property
Public Sub DisplayData()
Console.WriteLine("{0} {1}", EmployeeId, Department)
End Sub
End Class
' This Class creates Employee records by passing
' argumemnts to the constructor.
Public Class EmployeeManagerConstructor
Dim employees as Collection(Of EmployeeRecord) = _
new Collection(Of EmployeeRecord)()
Public Sub AddEmployee(employeeId as Integer, department as Integer)
Dim record as EmployeeRecord = new EmployeeRecord(employeeId, department)
employees.Add(record)
record.DisplayData()
End Sub
End Class
' This Class creates Employee records by setting properties.
Public Class EmployeeManagerProperties
Dim employees as Collection(Of EmployeeRecord)= _
new Collection(Of EmployeeRecord)()
Public Sub AddEmployee(employeeId as Integer, department as Integer)
Dim record as EmployeeRecord = new EmployeeRecord()
record.EmployeeId = employeeId
record.Department = department
employees.Add(record)
record.DisplayData()
End Sub
End Class
Public Class Tester
' The following method creates objects with the same state
' using the two different approaches.
Public Shared Sub Main()
Dim byConstructor as EmployeeManagerConstructor = _
new EmployeeManagerConstructor()
byConstructor.AddEmployee(102, 102)
Dim byProperties as EmployeeManagerProperties = _
new EmployeeManagerProperties()
byProperties.AddEmployee(102, 102)
End Sub
End Class
End Namespace
using System;
using System.Collections.ObjectModel;
namespace Examples.DesignGuidelines.Constructors
{
// This class can get its data either by setting
// properties or by passing the data to its constructor.
public class EmployeeRecord
{
private int employeeId;
private int department;
public EmployeeRecord()
{
}
public EmployeeRecord(int id, int department)
{
this.employeeId = id;
this.department = department;
}
public int Department
{
get {return department;}
set {department = value;}
}
public int EmployeeId
{
get {return employeeId;}
set {employeeId = value;}
}
public void DisplayData()
{
Console.WriteLine("{0} {1}", EmployeeId, Department);
}
}
// This class creates Employee records by passing
// argumemnts to the constructor.
public class EmployeeManagerConstructor
{
Collection<EmployeeRecord > employees = new Collection<EmployeeRecord>();
public void AddEmployee(int employeeId, int department)
{
EmployeeRecord record = new EmployeeRecord(employeeId, department);
employees.Add(record);
record.DisplayData();
}
}
// This class creates Employee records by setting properties.
public class EmployeeManagerProperties
{
Collection<EmployeeRecord > employees = new Collection<EmployeeRecord>();
public void AddEmployee(int employeeId, int department)
{
EmployeeRecord record = new EmployeeRecord();
record.EmployeeId = employeeId;
record.Department = department;
employees.Add(record);
record.DisplayData();
}
}
public class Tester
{
// The following method creates objects with the same state
// using the two different approaches.
public static void Main()
{
EmployeeManagerConstructor byConstructor =
new EmployeeManagerConstructor();
byConstructor.AddEmployee(102, 102);
EmployeeManagerProperties byProperties =
new EmployeeManagerProperties();
byProperties.AddEmployee(102, 102);
}
}
}
using namespace System;
using namespace System::Collections::ObjectModel;
namespace Examples { namespace DesignGuidelines { namespace Constructors
{
// This class can get its data either by setting
// properties or by passing the data to its constructor.
public ref class EmployeeRecord
{
private:
int employeeId;
int department;
public:
EmployeeRecord()
{
}
EmployeeRecord(int id, int department)
{
this->employeeId = id;
this->department = department;
}
property int Department
{
int get() {return department;}
void set(int value) {department = value;}
}
property int EmployeeId
{
int get() {return employeeId;}
void set(int value) {employeeId = value;}
}
void DisplayData()
{
Console::WriteLine("{0} {1}", EmployeeId, Department);
}
};
// This class creates Employee records by passing
// argumemnts to the constructor.
public ref class EmployeeManagerConstructor
{
private:
Collection<EmployeeRecord^>^ employees;
public:
EmployeeManagerConstructor()
{
employees = gcnew Collection<EmployeeRecord^>();
}
void AddEmployee(int employeeId, int department)
{
EmployeeRecord^ record = gcnew EmployeeRecord(employeeId, department);
employees->Add(record);
record->DisplayData();
}
};
// This class creates Employee records by setting properties.
public ref class EmployeeManagerProperties
{
private:
Collection<EmployeeRecord^>^ employees;
public:
EmployeeManagerProperties()
{
employees = gcnew Collection<EmployeeRecord^>();
}
void AddEmployee(int employeeId, int department)
{
EmployeeRecord^ record = gcnew EmployeeRecord();
record->EmployeeId = employeeId;
record->Department = department;
employees->Add(record);
record->DisplayData();
}
};
public ref class Tester
{
// The following method creates objects with the same state
// using the two different approaches.
public:
static void Main()
{
EmployeeManagerConstructor^ byConstructor =
gcnew EmployeeManagerConstructor();
byConstructor->AddEmployee(102, 102);
EmployeeManagerProperties^ byProperties =
gcnew EmployeeManagerProperties();
byProperties->AddEmployee(102, 102);
}
};
}}}
これらの例および適切にデザインされたライブラリでは、いずれの手法を使用してもオブジェクトが同じ状態で作成されます。 開発者がどちらの手法を使用するかは重要ではありません。
単にプロパティを設定するためにコンストラクター パラメーターを使用する場合は、コンストラクター パラメーターとプロパティに同じ名前を使用してください。 このようなパラメーターとプロパティの間の違いは、大文字と小文字の違いだけにする必要があります。
このガイドラインについては、前の例に示しています。
コンストラクターでの処理は最小限にしてください。 コンストラクターでは、コンストラクター パラメーターをキャプチャする以外の処理を実行しないでください。 これによって、要求されるまでの間、その他の処理を実行することによってパフォーマンスに影響が及ぶことがなくなります。
適切な場合、インスタンス コンストラクターから例外をスローしてください。
メソッドと同様に、コンストラクターでは例外をスローし、処理する必要があります。 コンストラクターでは特に、処理できない例外をキャッチしたり、隠したりしないようにする必要があります。 例外の詳細については、「例外のデザインのガイドライン」を参照してください。
既定のパブリック コンストラクターが必要な場合は、クラスで明示的に宣言してください。
クラスが既定のコンストラクターをサポートする場合は、それを明示的に宣言するのが最適な手順です。 一部のコンパイラでは、既定のコンストラクターが自動的にクラスに追加されますが、既定のコンストラクターを明示的に追加すると、コードの保守が簡単になります。 また、パラメーターを受け取るコンストラクターを追加することによって、コンパイラが出力を停止した場合でも、既定のコンストラクターの定義がそのまま維持されます。
既定のコンストラクターを構造体に配置するのは避けてください。
C# コンパイラを含む多くのコンパイラが、構造体でのパラメーターなしのコンストラクターをサポートしていません。
コンストラクター内部のオブジェクトで仮想メンバーを呼び出さないでください。
仮想メンバーを呼び出すと、最派生オーバーライドを定義している型のコンストラクターが呼び出されたかどうかに関係なく、最派生オーバーライドが呼び出されます。 この問題を次のコード例に示します。 基本クラスのコンストラクターを実行すると、派生クラスのコンストラクターが呼び出されていない場合でも、派生クラス メンバーが呼び出されます。 このコード例は、ビューステート フィールドが DerivedFromBad コンストラクターによって更新されていないことを示す BadBaseClass を出力します。
Imports System
Namespace Examples.DesignGuidelines.MemberDesign
Public Class BadBaseClass
Protected state as String
Public Sub New()
state = "BadBaseClass"
SetState()
End Sub
Public Overridable Sub SetState()
End Sub
End Class
Public Class DerivedFromBad
Inherits BadBaseClass
Public Sub New()
state = "DerivedFromBad "
End Sub
Public Overrides Sub SetState()
Console.WriteLine(state)
End Sub
End Class
Public Class tester
Public Shared Sub Main()
Dim b as DerivedFromBad = new DerivedFromBad()
End Sub
End Class
End Namespace
using System;
namespace Examples.DesignGuidelines.MemberDesign
{
public class BadBaseClass
{
protected string state;
public BadBaseClass()
{
state = "BadBaseClass";
SetState();
}
public virtual void SetState()
{
}
}
public class DerivedFromBad : BadBaseClass
{
public DerivedFromBad()
{
state = "DerivedFromBad ";
}
public override void SetState()
{
Console.WriteLine(state);
}
}
public class tester
{
public static void Main()
{
DerivedFromBad b = new DerivedFromBad();
}
}
}
using namespace System;
namespace Examples { namespace DesignGuidelines { namespace MemberDesign
{
public ref class BadBaseClass
{
protected:
String^ state;
public:
BadBaseClass()
{
state = "BadBaseClass";
SetState();
}
virtual void SetState()
{
}
};
public ref class DerivedFromBad : public BadBaseClass
{
public:
DerivedFromBad()
{
state = "DerivedFromBad ";
}
virtual void SetState() override
{
Console::WriteLine(state);
}
};
public ref class tester
{
public:
static void Main()
{
DerivedFromBad^ b = gcnew DerivedFromBad();
}
};
}}}
Portions Copyright 2005 Microsoft Corporation. All rights reserved.
Portions Copyright Addison-Wesley Corporation. All rights reserved.
設計ガイドラインの詳細についてを参照してください、「フレームワークの設計ガイドライン。規則、慣用句、および再利用可能なパターン。ネット ライブラリ」本クシシュトフ Cwalina、ブラッド エイブラムス、アスキー、2005 年発表しました。