DEV Community

mohamed Tayel
mohamed Tayel

Posted on • Edited on

Pros and Cons of Primary Constructors in C# 12

Meta Description

Discover how Primary Constructors in C# 12 simplify class definitions by reducing boilerplate and enhancing readability. Learn the differences between traditional constructors and Primary Constructors with clear examples, benefits, limitations, and full code demonstrations.

With the introduction of C# 12, developers gain access to new features designed to simplify class initialization. Primary Constructors allow properties to be declared and initialized directly in the class header, removing the need for explicit fields and constructor methods.

This article covers:

Best use cases for Primary Constructors.

Scenarios where Primary Constructors should be avoided.

📌 Full code examples for both ideal and avoidable scenarios.


🚀 Understanding Primary Constructors

Primary Constructors allow parameters to be declared in the class header, automatically treating them as readonly properties.

Example: Traditional Constructor vs. Primary Constructor

Using a Traditional Constructor

public class CustomerBefore
{
    private string _name;
    private int _age;

    public CustomerBefore(string name, int age)
    {
        _name = name;
        _age = age;
    }

    public string GetCustomerInfo()
    {
        return $"Customer: {_name}, Age: {_age}";
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Explicit fields (_name, _age) must be defined.
  • The constructor manually assigns values.
  • Requires additional getter methods for external access.

Using a Primary Constructor (C# 12)

public class CustomerAfter(string Name, int Age)
{
    public string GetCustomerInfo()
    {
        return $"Customer: {Name}, Age: {Age}";
    }
}
Enter fullscreen mode Exit fullscreen mode
  • Name and Age are implicitly readonly properties.
  • No explicit fields or constructor logic is required.
  • More concise and readable.

Usage Comparison

CustomerAfter customer = new CustomerAfter("Mohamed", 30);
Console.WriteLine(customer.GetCustomerInfo());

CustomerBefore oldCustomer = new CustomerBefore("Ahmed", 40);
Console.WriteLine(oldCustomer.GetCustomerInfo());
Enter fullscreen mode Exit fullscreen mode

When to Use Primary Constructors (Ideal Use Cases)

1️⃣ Simple Data-Holder Classes

Primary Constructors are perfect for small immutable objects that only store data.

public class Coordinate(double Latitude, double Longitude)
{
    public string GetLocation() => $"Lat: {Latitude}, Long: {Longitude}";
}

// Usage
var location = new Coordinate(40.7128, -74.0060);
Console.WriteLine(location.GetLocation());
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Minimal code for storing values.
  • Immutable by default → prevents accidental changes.

2️⃣ Immutable Configuration Models

Primary Constructors work well for immutable application settings.

public class AppConfig(string DatabaseUrl, int MaxConnections)
{
    public void DisplayConfig() => 
        Console.WriteLine($"DB: {DatabaseUrl}, Max Connections: {MaxConnections}");
}

// Usage
var config = new AppConfig("https://mydb.com", 100);
config.DisplayConfig();
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Encourages immutability → values cannot be modified after initialization.
  • Ideal for configuration models that remain unchanged.

3️⃣ DTOs (Data Transfer Objects)

DTOs transfer data between layers but do not contain business logic.

public class UserDto(string Name, string Email)
{
    public void PrintDetails() => Console.WriteLine($"User: {Name}, Email: {Email}");
}

// Usage
var user = new UserDto("Mohamed", "mohamed@example.com");
user.PrintDetails();
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Lightweight & immutable → ensures data consistency.
  • No unnecessary logic inside DTOs.

4️⃣ Lightweight Classes with Minimal Properties

For classes with only a few properties, Primary Constructors simplify the structure.

public class Product(string Name, decimal Price)
{
    public string GetInfo() => $"Product: {Name}, Price: {Price:C}";
}

// Usage
var product = new Product("Laptop", 1500.99m);
Console.WriteLine(product.GetInfo());
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Short, clean, and efficient representation of data.

When to Avoid Primary Constructors (Limitations & Fixes)

1️⃣ Classes Requiring Custom Property Logic

Primary Constructors cannot include property validation or computed values.

Incorrect Approach (Fails Compilation)

public class User(string Name, int Age)
{
    if (Age < 0) throw new ArgumentException("Age cannot be negative"); // ❌ Not allowed in Primary Constructor
}
Enter fullscreen mode Exit fullscreen mode

Correct Approach (Using Traditional Constructor)

public class User
{
    public string Name { get; }
    public int Age { get; }

    public User(string name, int age)
    {
        if (age < 0) throw new ArgumentException("Age cannot be negative");
        Name = name;
        Age = age;
    }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Primary Constructors lack validation logic.
  • Traditional constructors allow data validation before assignment.

2️⃣ Mutable Objects (Requires Property Modification)

Primary Constructor properties are readonly, making them unsuitable for objects that require modifications.

Incorrect Approach

public class BankAccount(string AccountNumber, decimal Balance)
{
    public void Deposit(decimal amount) => Balance += amount; // ❌ Read-only, cannot modify
}
Enter fullscreen mode Exit fullscreen mode

Correct Approach (Using Setter)

public class BankAccount
{
    public string AccountNumber { get; }
    public decimal Balance { get; private set; }

    public BankAccount(string accountNumber, decimal balance)
    {
        AccountNumber = accountNumber;
        Balance = balance;
    }

    public void Deposit(decimal amount)
    {
        Balance += amount;
    }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Primary Constructor properties cannot be modified.
  • Explicit setter allows controlled modifications.

3️⃣ Inheritance-Based Designs (Primary Constructors Don't Support Inheritance)

Primary Constructors do not support base class initialization.

Incorrect Approach

public class Animal(string Name) { }
public class Dog(string Name, string Breed) : Animal(Name) { } // ❌ Not Allowed
Enter fullscreen mode Exit fullscreen mode

Correct Approach (Using Base Constructor)

public class Animal
{
    public string Name { get; }

    public Animal(string name)
    {
        Name = name;
    }
}

public class Dog : Animal
{
    public string Breed { get; }

    public Dog(string name, string breed) : base(name)
    {
        Breed = breed;
    }
}
Enter fullscreen mode Exit fullscreen mode

✔️ Why?

  • Primary Constructors do not support base class constructor calls.
  • Use a traditional constructor to inherit parameters.

🎯 Final Thoughts

Use Primary Constructors for:

✔️ Simple data-holder classes.

✔️ Immutable settings/configuration models.

✔️ DTOs and lightweight objects.

Avoid Primary Constructors when:

❌ You need property validation.

❌ The class requires property modification.

❌ The class inherits from another class.

By understanding when and how to use Primary Constructors, you can write **cleaner, more maintainable C# code. 🚀**

Top comments (1)

Collapse
 
moh_moh701 profile image
mohamed Tayel • Edited