C# tutorials > Modern C# Features > C# 6.0 and Later > What are primary constructors in C# 12 and how do they simplify class definitions?

What are primary constructors in C# 12 and how do they simplify class definitions?

C# 12 introduces primary constructors, a concise way to declare class constructors and initialize properties directly in the class declaration. This feature reduces boilerplate code and enhances readability, making class definitions more streamlined.

Basic Syntax

In this example, (string firstName, string lastName) within the class declaration is the primary constructor. firstName and lastName are parameters that can be directly used within the class scope.

The FullName property utilizes these parameters to construct the full name.

public class Person(string firstName, string lastName)
{
    public string FullName => $"{firstName} {lastName}";
}

Equivalence to Traditional Constructor

The primary constructor approach is equivalent to the traditional constructor but is significantly more compact. The traditional constructor requires declaring private fields and assigning values within the constructor body.

public class Person
{
    private readonly string _firstName;
    private readonly string _lastName;

    public Person(string firstName, string lastName)
    {
        _firstName = firstName;
        _lastName = lastName;
    }

    public string FullName => $"{_firstName} {_lastName}";
}

Using Primary Constructor Parameters in Class Scope

Primary constructor parameters are in scope for the entire class. You can use them to initialize properties, in expression-bodied members, and in method implementations.

In this example, name is used to initialize the Name property, and price is used to calculate the DiscountedPrice and format the output of ToString.

public class Product(string name, decimal price)
{
    public string Name { get; } = name;  // Initialize property directly
    public decimal DiscountedPrice => price * 0.9m; //Use in expression-bodied member

    public override string ToString() => $"Product: {Name}, Price: {price:C}";
}

Immutability and Readonly Fields

To enforce immutability, you can assign primary constructor parameters to readonly fields. This ensures that the values cannot be modified after the object is created.

In this case, serverAddress is assigned to the readonly field _address.

public class Configuration(string serverAddress)
{
    private readonly string _address = serverAddress;
    public string Address => _address;
}

Combining Primary and Secondary Constructors

You can combine primary constructors with secondary constructors using constructor chaining (: this(...)). This allows you to provide multiple ways to create instances of the class.

In this example, the secondary constructor calls the primary constructor with a default value for studentId.

public class Student(string firstName, string lastName, int studentId)
{
    public Student(string firstName, string lastName) : this(firstName, lastName, 0) {}

    public string FullName => $"{firstName} {lastName}";
    public int StudentId { get; } = studentId;
}

Real-Life Use Case Section

Configuration Objects: Primary constructors are excellent for creating configuration objects where you want to encapsulate settings. For instance, consider a class representing database connection settings. The server address, database name, and credentials can be passed directly through the primary constructor. This promotes immutability and simplifies the initialization process.

Data Transfer Objects (DTOs): DTOs are often simple classes used to transfer data between layers of an application. Primary constructors can significantly reduce the boilerplate code needed to define these classes, making them more concise and readable.

Immutable Value Types: When creating immutable value types (e.g., representing a point in 2D space), primary constructors provide a clean and efficient way to initialize the fields.

Best Practices

Use Meaningful Parameter Names: Choose descriptive names for the parameters in your primary constructor to improve code clarity.

Keep it Simple: Primary constructors are best suited for simple initialization logic. Avoid complex computations or side effects within the primary constructor itself.

Consider Immutability: Whenever possible, use readonly fields to enforce immutability when using primary constructors.

Interview Tip

When discussing primary constructors in an interview, emphasize their role in reducing boilerplate code and improving code readability. Be prepared to explain how they relate to traditional constructors and the concept of immutability. Demonstrate your understanding of constructor chaining and how to combine primary and secondary constructors.

When to use them

Use primary constructors when you want to simplify class definitions and reduce boilerplate code, especially for classes with simple initialization logic.

They are particularly useful for creating immutable data structures, configuration objects, and DTOs.

Memory footprint

The memory footprint of a class using a primary constructor is generally the same as a class with a traditional constructor. The compiler generates similar IL code for both approaches. The primary advantage is in code conciseness and readability rather than memory optimization.

Alternatives

The main alternative to primary constructors is the traditional constructor syntax. Before C# 12, this was the only way to initialize class members through constructor parameters. Another alternative, especially in simpler cases, is object initialization syntax, but this doesn't provide the same level of encapsulation or enforce required parameters.

Pros

Conciseness: Reduces boilerplate code, making class definitions more compact.

Readability: Improves code readability by centralizing constructor logic in the class declaration.

Immutability: Encourages the use of readonly fields, promoting immutability.

Cons

Complexity: Can become less readable if the initialization logic within the primary constructor is too complex.

Limited Scope: Not suitable for scenarios requiring complex constructor logic or extensive validation.

FAQ

  • Can I have multiple primary constructors?

    No, you can only have one primary constructor per class. However, you can combine it with secondary constructors using constructor chaining.
  • Are primary constructors supported in older versions of C#?

    No, primary constructors were introduced in C# 12. You need to use C# 12 or later to use this feature.
  • Can I use primary constructors with structs?

    Yes, primary constructors are also supported for structs in C# 12.