AlgoMaster Logo

Constructors

Last Updated: May 17, 2026

12 min read

Declaring a class and creating objects from it with new does more than allocate memory: it runs a special method called a constructor, whose job is to put the object into a valid initial state. This chapter is about how constructors work, how to define your own, how to overload them, how to chain them within the same class, and how a separate animal called a static constructor fits into the picture. By the end you should be able to look at any class and predict exactly which code runs, and in what order, when someone writes new YourClass(...).

Why Constructors Exist

A class without a constructor is fine, but a class with no way to initialize its fields is mostly useless. Take a Product with three fields:

Every field gets its type's default value: null for string (printed as an empty string), 0 for decimal, 0 for int. The object exists, but it doesn't represent a real product. Someone now has to set each field by hand before this object is good for anything, and there's no compiler check to make sure they do.

A constructor solves this. It's a method that runs automatically when you write new Product(...), and you can use it to require the caller to supply the values needed to make a valid object.

Now new Product() won't compile. The only way to create a Product is through the constructor, and the constructor demands the three values it needs. The object can't exist in the broken half-built state from the first example.

That's the whole point: a constructor is the gate between "raw memory" and "a usable object." Everything else in this chapter is variation on the same theme.

Constructor Syntax

A constructor looks like a method with three rules:

  1. Its name is exactly the class name. No return type, not even void.
  2. It runs once per object, when you call new ClassName(...).
  3. It can have parameters, like any method, and you call it with arguments at the new site.

The new Customer("Alice", "alice@shop.com") call does three things in order: allocates memory for a Customer object on the heap, runs the constructor body with the arguments bound to the parameters, and returns a reference to the new object. The variable c holds that reference.

A constructor can have any access modifier (public, internal, private, protected), but most are public because that's what lets outside code create instances. We'll see one use of a private constructor near the end.

The Default Constructor

If you don't write any constructor at all, C# gives you one for free. It's called the default constructor: it takes no parameters and does nothing except initialize each field to its type's default value (0, null, false, etc.). This is what made new Product() work in the very first example.

The catch: the moment you declare any constructor of your own, the free default constructor goes away. The compiler stops generating it.

If you still want a parameterless constructor after adding a parameterized one, you have to declare it yourself:

The explicit parameterless constructor sets Owner to "Guest". It's not the same as the free default constructor (which would have left Owner as null), because you're free to put any code you like in the body.

Parameterized Constructors

A constructor with parameters is the most common shape. It forces the caller to supply enough information to build a usable object, and it gives the class a place to validate that information before the object exists.

The validation runs before the assignment. If any check fails, the constructor throws, and new Product(...) never produces an object: the bad variable is never assigned. This is the central guarantee constructors give you: by the time you hold a reference to a Product, that Product has already passed every check the constructor enforces.

Compare that to setting fields after construction. There's no way to require it, and no way to validate, because the object already exists. A class designed around its constructor pushes invalid state out of the universe entirely. That's a strong design lever and one of the main reasons constructors exist as a separate concept from regular methods.

Constructor Overloading

A class can declare more than one constructor, as long as the parameter lists differ in number or type. The compiler picks the matching constructor based on the arguments at the new site. This is just method overloading applied to constructors.

Three constructors, each with a different "shape" of caller input. The compiler resolves new Order(2, "Bob", 49.99m) to the three-argument constructor and new Order(1, "Alice") to the two-argument one.

This works, but look closely. Each constructor repeats the same field assignments. If you ever change how Status is initialized, you have to remember to update three places. That's the kind of duplication that quietly rots a codebase. Constructor chaining, coming up next, fixes it.

Overloading vs Optional Parameters

Optional parameters with default values cover a lot of the same ground:

One constructor, four call shapes. So when do you reach for overloading instead of optional parameters? A few cases:

  • The constructors do different work, not just default values. If one constructor parses a string and another takes the parsed pieces directly, those are genuinely different operations and overloads make that clearer.
  • You want to vary parameter types, not just count. new Money(100) and new Money("USD 100") can't share one signature.
  • You're writing a library and care about binary compatibility. Adding an overload is safer than changing a default value, since defaults are baked at the call site.

For pure "more parameters means more configurability," optional parameters are usually the lighter choice.

Expression-Bodied Constructors

For a constructor whose body is a single statement, C# 7 added expression-bodied syntax with =>. It's the same shorthand you've seen for methods.

becomes

Both versions compile to the same IL. The expression-bodied form is just less noise for one-line bodies.

Price and Stock got their type defaults because the constructor only set Name.

The expression-bodied form has a hard limit: one statement. The second you need two field assignments or a validation check, you go back to the block form. Don't try to cram multiple statements into the expression body with the comma operator or sequence tricks; the syntax doesn't support it, and even if it did, the result would be unreadable.

Constructor Chaining with : this(...)

The duplicated assignments in the Order overload example were a symptom: those three constructors were really one constructor with two shortcut versions. C# lets you express that directly. A constructor can call another constructor of the same class using : this(...) after its parameter list. The chained constructor runs first, then your body runs.

Same result as before, but the field-assignment logic lives in exactly one place. The two shorter constructors have empty bodies ({ }) because all their work is "delegated" to the four-argument constructor through : this(...). If you later need to validate customer, you add the check once, in the four-argument constructor, and every overload picks it up.

A quick diagram of how a new Order(1, "Alice") call flows:

The two-argument constructor never assigns fields directly. It hands control to the four-argument constructor through : this(...), which does the work and returns. Then the two-argument constructor's body would run (if it had one), and finally the new object is handed back to the caller.

A useful pattern: pick one constructor as the "primary" one that does all the real work, and have every other constructor chain to it. New developers reading the class can find the validation and assignment logic in one place. This pattern is sometimes called the delegating constructor or funnel pattern.

Field Initializers

Before we get to static constructors, one more place where initialization code can live: directly on the field declaration. You can give a field a starting value right where it's declared.

The = "Guest", = 0m, and = new List<string>() are field initializers. They run for every new instance, before the constructor body. So if a class has both field initializers and a constructor, the field initializers go first, then the constructor body.

If the constructor assigns the same field, its assignment overwrites the initializer's value. Don't use field initializers and constructor assignments to set the same field, since one of them is dead code by definition. Pick one place per field.

Field initializers are a clean way to set a default value that's the same for every instance, like Items = new List<string>() or Status = "Placed". They can't reference constructor parameters (the parameter doesn't exist yet when the initializer runs) or call any non-static instance member of the class being constructed.

Static Constructors

Everything so far has been about initializing one instance at a time. A static constructor is different. It runs once per type, not once per object, and it's where you put initialization code for static fields.

A static field belongs to the class itself, not to any instance. There's one copy of it shared across the whole program, no matter how many instances you create. Examples are things like a counter for "how many Order objects have ever been created" or a shared lookup table.

TotalOrdersCreated got its default value of 0 automatically. But what if you want a more involved setup for a static field, say loading a list of valid order statuses from somewhere? That's what a static constructor is for.

The static constructor (static Order()) ran exactly once, right before the first time the runtime needed the class. That happened to be the first new Order(...) call. After that, no matter how many Order objects we create, the static constructor never runs again.

Rules for Static Constructors

Static constructors have a tight set of rules, all of which exist because they run automatically rather than at your request:

  • No access modifier. You can't write public static Order() or private static Order(). The runtime is the only caller, so visibility is meaningless. Just static Order().
  • No parameters. Again, you don't call it, so there's nowhere for arguments to come from.
  • Exactly one per class. No overloads.
  • Runs at most once per type, per process, per AppDomain. After it runs, it never runs again.
  • Runs automatically, before the first time any of these happen: an instance is created, a static member is accessed, or a static method is called. You don't trigger it explicitly.

When Static Constructors Are Useful

The honest answer is "less often than you might think." Most static fields can use a field initializer:

That's a single-line initializer doing the same job as a static constructor. You only need an actual static constructor when:

  • The initialization is too involved for one expression (loops, conditionals, multiple statements).
  • You want to control initialization order between multiple static fields that depend on each other.
  • You need to catch exceptions during static initialization and log them.

For everything else, prefer field initializers. They're more direct and harder to misuse.

The Order of Initialization

Pulling all the pieces together, here's the exact sequence that runs when you create an instance of a class for the first time. Knowing this order is the difference between confidently predicting behavior and guessing.

For the first ever use of a type in a program:

  1. Static field initializers run, in the order the fields are declared in the source file.
  2. Static constructor runs (if one is declared).
  3. Instance field initializers run, in declaration order.
  4. Instance constructor body runs.

For every subsequent instance, steps 1 and 2 are already done and don't run again. Only steps 3 and 4 run.

The static side runs exactly once, at the boundary between "we've never touched this type" and "we're using it now." The instance side runs every time a new object is built.

Here's the same sequence as a flow diagram, which is sometimes easier to keep in your head:

The "Yes" branch is the common case after the first use: skip the static work, run the instance work, return. The "No" branch happens exactly once per type per process.

This order is also why static initializers can't refer to instance members and why instance initializers can refer to static members. By the time an instance initializer runs, all the static state is already in place. The reverse isn't true.

A Practical Example

Stepping back from the rules, here's a ShoppingCart class that uses several of the features from this chapter together:

A few things worth noticing:

  • The two shorter constructors chain to the two-argument one. The validation lives in one place and applies no matter which constructor a caller picks.
  • Items and Total are set through field initializers because every cart starts the same way. There's no reason to repeat Items = new List<string>() in three constructors.
  • CartsCreated is incremented inside the two-argument constructor. Because the other two chain to it, every cart creation counts exactly once. If we'd increment in each constructor instead, calls through the chain would double-count.
  • No static constructor is needed. CartsCreated = 0 is a simple enough initializer to live on the field.

This is the shape most real C# classes end up in: one well-validated "main" constructor, one or more chained shortcuts, field initializers for "every instance starts this way" state, and a static field or two if the class needs to track something across all instances.

Summary

  • A constructor is a special method named after the class with no return type. It runs once per new call and initializes the object before the reference is handed back.
  • If you don't declare any constructor, C# generates a free parameterless one that leaves fields at their type defaults. The moment you declare any constructor, that free default disappears.
  • Parameterized constructors are how a class enforces "you can't have an instance without supplying these values," and where you put validation that must pass before the object exists.
  • Constructors can be overloaded by parameter list. Use overloads when constructors do genuinely different work, and optional parameters when they only differ in defaults.
  • Expression-bodied constructors (public T(...) => Field = value;) are syntactic sugar for one-statement constructors.
  • : this(...) chains one constructor to another in the same class. Use it to funnel every overload through a single "primary" constructor that holds the validation and assignment logic.
  • A static constructor (static ClassName()) runs exactly once per type per process, automatically, before the type is first used. It has no access modifier and no parameters.
  • Initialization order for a first instance: static field initializers, then static constructor, then instance field initializers, then instance constructor body. For later instances, only the instance steps run.

The _this Keyword_ lesson covers the this keyword in more depth. We've already seen it in : this(...) constructor chaining. Inside any instance method or constructor, this is also a reference to the current object itself, and it has a handful of uses, from disambiguating field and parameter names to passing the current instance to another method.