C# Constructors and Initialization: Start Every Object with Valid State
A constructor establishes an object’s starting state. Learn to require useful inputs, store them in the right fields, reject invalid values and share initialization rules across constructor overloads.
What you will understand
- Declare a constructor with the class name and no return type.
- Supply required constructor arguments in a new expression.
- Explain when the compiler provides a parameterless constructor.
- Use this.field to store a same-named parameter correctly.
- Trace field initializers, constructor bodies and object initializers.
- Reject invalid input and distinguish failure from silent repair.
- Delegate a convenience constructor with : this(...).
- Verify valid creation and later state transitions separately.
Create an object ready for its first useful operation
Until now, our inventory examples created an item with placeholder defaults and then assigned values. A constructor moves required starting inputs into the creation expression. The caller can see that an item needs a name and opening stock before using it.
A constructor is named exactly like its class and has no return type, including no void. Its parameters work much like method parameters. The new expression creates and initializes the instance; the constructor body establishes its starting state. A constructor does not return this as an ordinary result.
In new InventoryItem("Pen", 5), the arguments reach the constructor, which stores them in fields. The first example intentionally has assignment without validation so you can learn the syntax first. We then strengthen the contract instead of assuming that any supplied values are meaningful.
Program.cs · Supply the required starting values at creation
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 5 in stock
The constructor has exactly the class name and no return type. new InventoryItem("Pen", 5) supplies its inputs. The constructor assigns fields before the expression successfully returns the new reference. This first version teaches assignment only; validation is added below.
Program.cs · A constructor must not declare void
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: a member cannot have the same name as its enclosing type when declared with a return type.void is a return type for an ordinary method, not constructor syntax. Remove void to declare a constructor. The same-name member shown here is invalid; it is not a constructor that returns nothing.
An implicit parameterless constructor is not always present
For an ordinary class that declares no instance constructor, C# supplies a parameterless constructor. Marker therefore supports new Marker() and begins with its field initializer. This convenience is a language rule, not an automatic promise that the resulting defaults are useful for your application.
Once InventoryItem declares its two-parameter constructor, the compiler no longer adds an extra parameterless one. new InventoryItem() cannot satisfy a contract that requires a name and stock. Read the declarations rather than assuming every class can always be created with empty parentheses.
You can deliberately declare more constructors, but each should describe a valid way to create the object. Do not add an empty constructor only to remove an error if it produces a nameless item. The compiler checks that required arguments are supplied; the constructor must still check application-level rules about their values.
Program.cs · No declared constructor means an implicit parameterless one
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Ready
Marker declares no instance constructor, so the compiler supplies a parameterless one. Its field initializer supplies Ready. This rule applies to these ordinary classes; declaring an explicit parameterized constructor does not preserve an extra implicit parameterless overload.
Program.cs · An explicit constructor changes which creation calls exist
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: no argument was supplied for required constructor parameter name.Only the two-argument constructor is declared. C# does not also add a parameterless constructor. Supply the required name and stock rather than inventing a no-argument overload when no useful default identity exists.
Store parameters on the current instance
Constructor parameters are inputs to one invocation. Fields are the state remembered by the resulting object. When both use the name name, the parameter hides the unqualified field name inside the constructor body. this.name explicitly identifies the field on the object being initialized.
The assignment this.name = name reads the parameter and writes the field. Writing name = name instead reads and writes the parameter, leaving the field unchanged. The same distinction applies to stock. Naming fields differently can also avoid ambiguity, but you must still understand which storage each assignment changes.
Each new expression initializes its own instance. Creating a pen with stock five and a notebook with stock three does not reuse one shared constructor parameter as permanent storage. The values are stored in separate instance fields. Construction does not require those fields to become static.
Program.cs · Each creation receives its own constructor inputs
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 5 in stock
Notebook: 3 in stock
The same constructor definition initializes two separate instances with different inputs. Constructor parameters belong to their own invocation. this.name and this.stock store those values in the particular object being initialized.
Distinguish field defaults, constructor work and object initializers
For the simple non-derived classes here, fields first receive their language defaults, explicit field initializers run, and then the constructor body runs. An object initializer following the new expression performs its accessible member assignments after the constructor. Inheritance adds initialization details that later lessons discuss.
The Sample program deliberately prints inside its constructor to reveal that it sees the field initializer value five. The constructor then writes eight, and the object initializer overwrites it with twelve. This is a tracing demonstration, not a recommendation to print reports or perform external work during construction.
A constructor check cannot protect a publicly writable field from a later initializer or assignment. Keep important state behind a boundary and make subsequent operations enforce its rules too. Our inventory model uses private fields and a query, so an external initializer cannot silently replace valid stock with a negative value.
Program.cs · An object initializer runs after the constructor
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Constructor sees: 5
After initializer: 12
In this simple class, default field initialization is followed by the explicit field initializer, then the constructor body, then object-initializer assignments. The constructor sees five, writes eight and is later overwritten with twelve. Printing in the constructor is only a tracing aid; the inventory model keeps presentation outside its constructor.
Program.cs · Private fields prevent an object initializer from bypassing the contract
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Compilation fails: InventoryItem.stock is inaccessible due to its protection level.Constructor validation alone cannot protect a public mutable field from later assignments. Here stock is private, so this attempted initializer cannot write it. Supported later operations must also preserve valid state. More detailed access rules and properties follow in the next lessons.
Reject invalid creation at the boundary
Our contract accepts a nonblank name and nonnegative opening stock. Zero is valid because an item may be known before any units arrive. Negative stock is invalid for this exercise. Whitespace outside a valid name is trimmed deliberately, while an entirely blank name is rejected.
The keyword throw signals that construction cannot finish normally. ArgumentException identifies an invalid name, and ArgumentOutOfRangeException identifies negative stock. nameof(stock) produces the parameter name without a separate hard-coded string. Exceptions are covered fully later; these small examples introduce only enough syntax to make failed construction explicit.
The surrounding try block attempts construction and the catch handles the expected demonstration failure. If the constructor throws, the assignment in that expression does not complete and the caller receives no successfully initialized object from it. The examples print ParamName rather than a platform-dependent exception message. Silently changing a negative number to zero would conceal a different input error.
Program.cs · Reject invalid stock instead of silently repairing it
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected parameter: stock
throw signals failed construction and prevents the assignment from completing normally. The catch handles this expected demonstration failure so the program prints a stable explanation. The caller receives no successfully initialized item from this expression. Clamping negative stock to zero would hide the invalid input instead of rejecting it.
Program.cs · Reject missing names and deliberately normalize valid ones
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Rejected parameter: name
Pen: 0 in stock
IsNullOrWhiteSpace rejects null, empty and whitespace-only names. After that check, Trim removes outside whitespace from an accepted name. Zero stock is explicitly valid. Name normalization and invalid-input rejection are different decisions, both stated in the contract.
Delegate convenience constructors to one complete contract
Constructors can be overloaded with different parameter lists. A one-name constructor can mean opening stock zero if that is a sensible documented choice. The complete name-and-stock constructor remains the one place that performs validation and field assignment.
The initializer : this(name, 0) calls another constructor in the same class for the same object before the convenience body runs. This is not a method-body call and does not allocate a second object. The delegation must eventually reach a complete constructor rather than form a cycle.
Use constructor chaining to keep rules consistent. If both overloads duplicated checks, a later rule change might update only one path. This lesson uses traditional constructor bodies to keep each step visible. Primary constructors and broader design patterns are not needed to understand or complete this model.
Program.cs · A convenience constructor can delegate to the complete one
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 0 in stock
Notebook: 3 in stock
: this(name, 0) invokes the two-argument constructor for the same object before the short constructor body runs. It does not create a second InventoryItem. Both call forms reuse the same name and stock checks. Zero is a documented useful default; a missing name is not.
Test creation and later state changes separately
Test the smallest valid stock, a normal positive value, a negative value, and names that are empty or only whitespace. Also verify the documented trimming behavior for a valid name with outside spaces. A constructor test should check both rejection and the state of accepted objects.
Next test the object after successful creation. A valid item should support a valid sale, refuse an excessive or nonpositive request, and preserve nonnegative stock. Initial validation and later transition validation solve different parts of the same invariant: state must begin valid and remain valid through supported operations.
The repair below focuses on parameter shadowing; the lab adds the complete validation contract. Neither constructor sends email, saves a row, charges a buyer or fetches remote data. Keeping these examples focused makes the input rules and resulting state easy to inspect before larger application workflows are introduced.
Guided repair: store the supplied constructor inputs
Creating an item with Pen and five prints Unassigned and zero. The constructor assigns each parameter back to itself instead of storing it on the object. Repair only the field assignment first.
Program.cs · Parameters accidentally assign themselves
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Unassigned: 0 in stock
The parameters hide the field names. name = name and stock = stock assign parameter values back to themselves, leaving the initialized fields unchanged. The compiler can warn about these assignments, but the program can still run. Passing correct arguments does not guarantee that the body stored them correctly.
Repair checkpoints
- Identify name and stock as parameters inside the constructor.
- Use this.name = name to write the instance name field.
- Use this.stock = stock to write the instance stock field.
- Verify Pen: 5 in stock for the reference call.
- Add validation separately rather than confusing a storage bug with an input rule.
Reveal the repair and explanation
Program.cs · Use this to store the supplied values in fields
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 5 in stock
this.name selects the current object’s field and name on the right reads the constructor parameter. The same distinction applies to stock. This repair fixes the storage bug only; the validated version is used in the independent lab.
Independent lab: valid inventory from the first moment
Complete an inventory constructor that accepts only a meaningful name and nonnegative stock. Normalize outside name whitespace and keep the existing guarded sale behavior.
Acceptance checklist
- Keep name and stock private instance fields.
- Reject null, empty or whitespace-only name with ArgumentException identifying name.
- Reject negative stock with ArgumentOutOfRangeException identifying stock.
- Accept zero stock as a valid starting count.
- Store name.Trim() and the supplied stock using this.field assignments.
- Keep the existing TrySell guard and leave console output in Main.
- The reference inputs print Pen: 5 in stock, Sale accepted: True, then Pen: 3 in stock.
Program.cs · Valid inventory initialization starter
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen : 5 in stock
Sale accepted: True
Pen : 3 in stock
The constructor stores supplied values but neither validates nor trims the name. The sale operation already guards later quantity changes. Complete initial-state validation separately; a guarded sale method cannot retroactively make invalid construction safe.
Show hints
- Check string.IsNullOrWhiteSpace(name) before calling Trim.
- Use nameof(name) and nameof(stock) for the relevant parameter identifiers.
- Do not clamp a negative count or invent a placeholder title to hide invalid input.
Reveal the reference solution
Program.cs · Validated initialization reference solution
Predict the result, edit Program.cs, then run your program.
Standard input · optional
Select Run code to compile and execute this program.
Pen: 5 in stock
Sale accepted: True
Pen: 3 in stock
The constructor rejects a missing name and negative stock before storing normalized valid fields. TrySell preserves nonnegative stock afterward. Both boundaries matter: valid creation and valid later transitions. The example is in memory and performs no persisted inventory or payment transaction.
Changed-input checks
- A name Pen with stock 0 succeeds and describes zero stock.
- A name with outside spaces is stored trimmed.
- Empty, whitespace-only and null names are rejected for name.
- Stock -1 is rejected for stock.
- A valid item with five units can sell all five, then refuses another sale.
- The convenience constructor, if added with : this(name, 0), applies the same name checks.
Recap: initialization is a contract
Identify required inputs, field assignments, rejection paths and the operations that preserve valid state afterward.
public InventoryItem(...)- Declare an accessible constructor with no return type.
new InventoryItem("Pen", 5)- Create and initialize an instance using required inputs.
this.name = name- Store the supplied parameter in the current object’s field.
implicit parameterless constructor- Provided for these ordinary classes only when no instance constructor is declared.
throw new ArgumentException(...)- Reject invalid argument data instead of completing construction normally.
nameof(stock)- Produce the parameter name for diagnostics.
: this(name, 0)- Delegate to another constructor of the same object.
private state plus guarded operations- Protect accepted starting state from unsupported later writes.
Frequently asked questions
Why is there no void before the constructor name?
A constructor has its class’s name and no return type. void belongs to ordinary methods that return no value; adding it does not declare a valid constructor.
Why did new InventoryItem() stop compiling?
Declaring an explicit parameterized instance constructor removes the implicit parameterless one. Supply the required inputs or deliberately define another valid constructor.
Does this(name, 0) create another object?
No. A constructor initializer delegates to another constructor on the same instance before the delegating body executes.
What if the constructor throws?
The new expression does not complete normally and its result is not assigned. Handle the failure at a suitable caller boundary; these examples catch expected invalid-input failures for explanation.
Do object initializers run before validation?
They run after the constructor completes. Publicly mutable members could therefore be changed afterward; private state and guarded operations matter beyond initial validation.
Is constructor validation enough forever?
No. Later supported operations must preserve the same invariants. A valid opening count does not make an unguarded subtraction safe.
Can you explain how construction succeeds or fails?
Check constructor syntax, initialization order, field assignments and rejection rules. Read the explanation after checking your answers.
Next step and references
A constructor makes required inputs explicit, stores them in the correct fields and rejects invalid creation. Delegate convenience paths to the same rules and preserve valid state through later operations.
Next is C# Access Modifiers. You will choose the public operations of a type and restrict implementation details without confusing accessibility with static ownership.
Official references
Original SovranCode explanations, examples and exercises. Technical details were checked against these references.
Last updated: .