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}";
}
}
-
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}";
}
}
-
Name
andAge
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());
β 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());
βοΈ 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();
βοΈ 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();
βοΈ 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());
βοΈ 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
}
β 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;
}
}
βοΈ 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
}
β 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;
}
}
βοΈ 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
β 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;
}
}
βοΈ 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)
git url
github.com/mohamedtayel1980/DotNet...