Constructors
Summary
A constructor is a special method that runs automatically when an object is created, responsible for putting that object into a valid starting state before any other code can use it. Instead of creating an object and then separately remembering to configure it correctly, a constructor guarantees every object begins life with its required data already set.
Why This Matters
Without a constructor, creating an object and initializing it are two
separate steps — var weapon = new Weapon(); weapon.Damage = 10; weapon.Name = "Sword"; — and nothing stops code from skipping the second step
entirely, leaving weapon.Name as null and weapon.Damage as 0. A bug
like that surfaces far from where the object was created, when something
finally tries to use the uninitialized data. A constructor that requires
Name and Damage up front — new Weapon("Sword", 10) — makes creating an
invalid object impossible to express in the first place.
Mental Model
A constructor is a factory's final assembly step, not an empty box handed
to the customer. Buying furniture that arrives fully assembled means it's
usable the moment it's unboxed — there's no separate "now attach the legs
or it'll be unstable" step someone might forget. A class with a constructor
that requires its essential data works the same way: by the time new Weapon("Sword", 10) returns, the object is already complete and safe to
use, rather than handed over "empty" and hoping the caller finishes the
job.
Core Explanation
- Runs exactly once, automatically, at creation. A constructor executes
the moment
new ClassName(...)is called, before the resulting reference is available to any other code — there is no way to create an instance and skip its constructor. - Shares the class's name and has no return type.
public Weapon(string name, int damage)— not evenvoid— signals it isn't a regular method returning a value, but the special routine that builds the object itself. - A class can have multiple constructors (overloading).
Weapon()andWeapon(string name, int damage)can coexist, giving callers a choice between a default and a fully-specified creation path (see [[CON-0027]]). this(...)chains one constructor to another to avoid duplicating initialization logic. A simpler constructor can delegate to a more complete one, supplying defaults for the parameters it doesn't take.
Syntax / Theory
C# (Unity — plain classes)
public class Weapon
{
public string Name { get; }
public int Damage { get; }
public Weapon(string name, int damage)
{
Name = name;
Damage = damage;
}
// Chains to the main constructor, supplying a default damage value.
public Weapon(string name) : this(name, damage: 10) { }
}
var sword = new Weapon("Sword", 25);
var dagger = new Weapon("Dagger"); // Uses the default-damage overload.
Note:
MonoBehaviour-derived classes are created by Unity attaching them to a GameObject, not by callingnewdirectly — useAwake()orStart()for their initialization instead of a constructor (see Common Mistakes).
C++ (Unreal)
class FWeapon
{
public:
FWeapon(const FString& InName, int32 InDamage)
: Name(InName), Damage(InDamage) // Member initializer list.
{
}
private:
FString Name;
int32 Damage;
};
AActor/UObject subclasses in Unreal use their own constructor for
component setup and default subobject creation, but full gameplay
initialization typically happens in BeginPlay(), mirroring Unity's
Awake()/Start() split from a plain-class constructor.
Practical Example
A configuration object that must always have a valid file path is a good candidate for constructor-enforced initialization.
public class SaveFile
{
public string Path { get; }
public DateTime CreatedAt { get; }
public SaveFile(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Save file path cannot be empty.");
Path = path;
CreatedAt = DateTime.Now;
}
}
var save = new SaveFile("slot1.json"); // Guaranteed valid the moment it exists.
There is no way to end up with a SaveFile that has a null or empty
Path — the constructor makes that state unrepresentable rather than
relying on every caller to remember a validation step.
Common Mistakes
- Trying to use
newon aMonoBehaviour. Unity components are attached viaAddComponent<T>()or exist in the scene already — a hand-called constructor on aMonoBehaviourruns, but Unity-specific setup (like accessingtransform) isn't valid there yet; useAwake()/Start()instead. - Doing heavy work inside a constructor. Loading a file, hitting the
network, or spawning other objects inside a constructor makes object
creation slow and unpredictable — prefer a separate
Initialize()method for anything beyond assigning fields from parameters. - Leaving a field uninitialized because it "will be set later." Defeats the entire purpose of a constructor — if a field truly must be set after construction, that's a signal it should be optional (with a sensible default) rather than required.
- Duplicating initialization logic across multiple constructors instead
of chaining with
this(...). Two constructors that each independently set the same three fields will eventually drift out of sync when one is updated and the other is forgotten.
Game Development Example
An enemy spawner creates fully-configured enemies from data, rather than creating a blank enemy and configuring it across several follow-up calls.
public class EnemyData
{
public string Name { get; }
public int MaxHealth { get; }
public float MoveSpeed { get; }
public EnemyData(string name, int maxHealth, float moveSpeed)
{
Name = name;
MaxHealth = maxHealth;
MoveSpeed = moveSpeed;
}
}
void SpawnWave(EnemyData data, int count)
{
for (int i = 0; i < count; i++)
{
var enemy = Instantiate(enemyPrefab);
enemy.Configure(data); // Enemy MonoBehaviour still uses Configure, not new.
}
}
Even where Unity's object model prevents a literal constructor on the
spawned MonoBehaviour, the plain EnemyData class still uses a
constructor to guarantee every wave definition is complete before it's
ever passed to SpawnWave.
Best Practices
- Require every field the object cannot function without as a constructor parameter; make anything truly optional a property with a sensible default instead.
- Keep constructor bodies limited to assignment and cheap validation — push
expensive setup into an explicit
Initialize()/Awake()step. - Use constructor chaining (
this(...)) to avoid duplicating initialization logic across overloads. - For
MonoBehaviours, treatAwake()as "the constructor equivalent" for self-contained setup, andStart()for setup that depends on other objects already existing.
Exercises
- Write a
Vector2Int-style plain class with anX/Yconstructor, then add a second constructor with no parameters that chains to the first withthis(0, 0). - Take the
SaveFileexample and add a second constructor overload that accepts a directory and a filename separately, combining them into the finalPath— chain it to the existing single-pathconstructor. - You're designing an
Itemclass whereNameandIdare always required butDescriptionandIconPathare often left as defaults. Decide between several constructor overloads versus one constructor plus optional/default parameters, and justify the choice. - Research C# object initializers (
new Weapon { Name = "Sword" }) and explain why they don't replace the need for a constructor when a class has fields that must never be left at their default value.
Related Concepts
- Objects — a constructor is the specific mechanism by which an object moves from "not existing" to "existing in a valid state"; understanding what an object is is the necessary prerequisite.
- Properties — constructors are the most common place inside a class
allowed to set a read-only or
private setproperty to its initial value. - Method Overloading — multiple constructors on one class are simply overloading applied to the constructor specifically.
Further Reading
KNOWLEDGE_SCHEMA.md— GDKB's concept maturity ladder and cross-linking rules, which this document follows.- Microsoft C# documentation — Instance Constructors: https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors
- Unreal Engine C++ documentation — Constructors: https://dev.epicgames.com/documentation/en-us/unreal-engine/unreal-engine-actor-lifecycle