← Back

Constructors & Object Initialization

2026-01-04 23:46 · 👁 17001

#c#

🚀 C# Tip — Constructors & Object Initialization (Classic → Modern) in .NET

C# constructors have evolved a lot in the last few versions. Today, you can choose between classic constructors, records, primary constructors, and required members—each with different trade-offs.

Here’s a practical guide to what matters in real .NET code 👇


1️⃣ Classic constructors (still the baseline)

Use when you need:

  • multiple overloads
  • validation / invariants
  • complex initialization logic
public sealed class Order
{
    public Guid Id { get; }
    public decimal Total { get; }

    public Order(Guid id, decimal total)
    {
        if (total < 0) throw new ArgumentOutOfRangeException(nameof(total));
        Id = id;
        Total = total;
    }
}

2️⃣ Records: “constructor-first” modeling

Records are great for immutable data models (DTOs, messages, snapshots):

public record OrderDto(Guid Id, decimal Total);

They generate value-based equality and keep your model concise.


3️⃣ Primary Constructors (C# 12+): less ceremony (great for DI)

Primary constructors let you declare constructor parameters in the type declaration, and use them throughout the type.

public sealed class OrdersService(IOrderRepository repo, ILogger<OrdersService> log)
{
    public Task<Order?> GetAsync(Guid id) => repo.GetByIdAsync(id);
}

✅ Ideal for “DI services with only assignments”
⚠️ If initialization/validation grows, a classic constructor can be clearer


4️⃣ required members (C# 11+): enforce initialization at compile time

With required, the compiler forces callers to initialize important state—either via object initializer or constructor rules.

public class Customer
{
    public required string Name { get; init; }
    public string? Email { get; init; }
}

var c = new Customer { Name = "Diego" }; // OK

✅ Great for DTOs / config objects
⚠️ Be careful: it’s a compile-time contract—design your public constructors with intent


5️⃣ Object initializers keep getting better (C# 13 highlight)

C# 13 improved object initializers with implicit “from the end” index (^) for single-dimensional collections.

var countdown = new TimerRemaining
{
    buffer = { [^1] = 0, [^2] = 1, [^3] = 2 }
};

This is small, but it improves readability in initialization-heavy code.


🧠 How to choose (quick rule of thumb)

  • Classic constructor → invariants + multiple ways to create the object
  • Record → immutable “data carrier”
  • Primary constructor → lean services / simple state wiring (DI-friendly)
  • required + init → enforce “must set” properties without constructor overload noise
  • Object initializers → great for DTO/config/build-up patterns

🎯 Final takeaway

Modern C# gives you multiple ways to express “how an object is born.”
Pick the one that makes intent obvious and invalid states hard.


#dotnet #csharp #architecture #cleanCode #softwareengineering #aspnetcore

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please reload the page.