Constructor Design
Constructors are special methods used to initialize types and create instances of types. A type constructor is used to initialize static data in a type. A type constructor is called by the common language runtime (CLR) before any instances of the type are created. Type constructors are static (Shared in Visual Basic) and cannot take parameters. An instance constructor is used to create instances of a type. Instance constructors can take parameters, but are not required to do so. An instance constructor with no parameters is called a default constructor.
The following guidelines describe the best practices for creating constructors.
Consider providing simple, ideally default, constructors. A simple constructor has a very small number of parameters, and all parameters are primitive types or enumerations.
Consider using a static factory method instead of a constructor if the semantics of the desired operation do not map directly to the construction of a new instance, or if following the constructor design guidelines feels unnatural.
Do use constructor parameters as shortcuts for setting main properties.
Setting properties by using the constructor should be identical to setting the properties directly. The following code example shows an EmployeeRecord class that can be initialized either by calling a constructor or by setting properties directly. The EmployeeManagerConstructor class demonstrates initializing an EmployeeRecord object using the constructor. The EmployeeManagerProperties class demonstrates initializing an EmployeeRecord object using properties. The Tester class demonstrates that regardless of the technique used, the objects have the same state.
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);
}
};
}}}
Note that in these examples, and in a well-designed library, both approaches create objects in the same state. It does not matter which approach a developer prefers to use.
Do use the same name for constructor parameters and a property, if the constructor parameters are used to simply set the property. The only difference between such parameters and the properties should be casing.
This guideline is illustrated in the previous example.
Do minimal work in the constructor. Constructors should not do much work other than to capture the constructor parameters. The cost of any other processing should be delayed until required.
Do throw exceptions from instance constructors if appropriate.
Constructors should throw and handle exceptions like any method. Specifically, a constructor should not catch and hide any exceptions that it cannot handle. For additional information on exceptions, see Design Guidelines for Exceptions.
Do explicitly declare the public default constructor in classes, if such a constructor is required.
It is a best practice to explicitly define a default constructor if your class supports it. Even though some compilers automatically add a default constructor to your class, adding it explicitly makes code maintenance easier. It also ensures the default constructor remains defined even if the compiler stops emitting it because you add a constructor that takes parameters.
Avoid having default constructors on structures.
Many compilers, including the C# compiler, do not support parameterless constructors on structures.
Do not call virtual members on an object inside its constructors.
Calling a virtual member causes the most-derived override to be called regardless of whether the constructor for the type that defines the most-derived override has been called. The following code example demonstrates this issue. As the base class constructor executes, it calls the derived class member, even though the derived class constructor has not been called. This example prints BadBaseClass to show that the state field has not been updated by the DerivedFromBad constructor.
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.
For more information on design guidelines, see the "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" book by Krzysztof Cwalina and Brad Abrams, published by Addison-Wesley, 2005.