top of page

Explore the Latest C# 12 Primary Constructor: What You Need to Know

C# 12 has a cool new feature called the primary constructor, and it's here to simplify how we set up our objects. Before, setting things up with constructors was a bit of a hassle, with too much repetitive code.


Old Problems, New Solution

Traditional constructors had a lot of extra baggage, making our code messy and harder to read. The primary constructor swoops in to fix this, making our lives as developers way easier.


Older Constructors Issues:

  • C# developers used conventional constructors before the primary constructor.

  • This led to code bloat and reduced readability.

  • The Repetitive nature, especially in classes with many properties, made clean and concise code a challenge.

  • Constructor syntax was particularly verbose when working with immutable types.

Enter C# 12 Primary Constructor:

  • C# 12 introduces the primary constructor as a modern solution.

  • Aims to simplify initialization and promote cleaner code.

  • Allows developers to focus on the core logic without unnecessary boilerplate code.


What's Inside

In this quick read, we'll check out:


What is a Primary Constructor?

Primary constructors are a new feature in C# 12 that allows you to declare constructor parameters directly within the class or struct declaration. This makes it more concise to define constructors and provides a more consistent way to access constructor parameters.


Syntax:

Primary constructors in C# 12 offer a concise syntax to declare constructors whose parameters are available anywhere in the body of the type.


Here’s an example of how to declare a primary constructor:

public class GeoPoint(decimal latitude, decimal longitude)
{
    // Class body goes here
}

In this example, latitude and longitude are the parameters of the primary constructor. These parameters are in scope throughout the class definition.


If you have other constructors in your class, they must call the primary constructor, directly or indirectly, through a this() constructor invocation. Here’s an example:

public class C(bool b, int i, string s) : B(b) 
// b passed to base constructor
{
    public int I { get; set; } = i; 
    // i used for initializationpublic string S 
    // s used directly in function members
    {
        get => s;
        set => s = value ?? throw new ArgumentNullException(nameof(S));
    }
    public C(string s) : this(true, 0, s) { } 
    // must call this (...)
}

In this example, the class C has a primary constructor with parameters b, i, and s. The class also has another constructor that takes a single parameter s and calls the primary constructor with this(true, 0, s).


Benefits of primary constructors:

  • Conciseness: Primary constructors eliminate the need to declare a separate constructor method, which can reduce code clutter and improve readability.

  • Consistency: Primary constructor parameters are available throughout the class or struct, making it easier to access and initialize properties or fields.

  • Dependency injection: Primary constructors can be used to facilitate dependency injection, simplifying the creation of objects with complex dependencies.


How to use Primary Constructors?

In C#, to define a primary constructor, simply list the constructor parameters within parentheses after the class or struct name. For example:

public class Person {     
    public string FirstName { get; }     
    public string LastName { get; }      
    
    public Person(string firstName, string lastName)     
    {         
        FirstName = firstName;         
        LastName = lastName;     
    }      
    
    public void Greet()     
    {         
        Console.WriteLine($"Hello, {FirstName} {LastName}!");     
    } 
} 

In this example, the Person class has a primary constructor that takes two parameters: firstName and lastName. These parameters are used to initialize the corresponding properties of the class. The class also has a Greet() method that prints a greeting message using the FirstName and LastName properties.


To create an instance of the Person class, you can use the following code:

var person = new Person("Rahul", "Roy"); 
person.Greet(); 

This code will create a new instance of the Person class with the first name "Rahul" and the last name "Roy". It will then call the Greet() method on the person object, which will print the following message to the console:

Hello, Rahul Roy! 

Restrictions on primary constructors:

  • A class or struct can only have one primary constructor.

  • Primary constructor parameters must be declared with access modifiers (e.g., public, private, etc.).

  • Primary constructor parameters cannot have default values.

  • If a class or struct has any other constructors, those constructors must call the primary constructor using the this() syntax.



When to use Primary Constructors?

Primary constructors are a good choice for classes or structs that have a small number of constructor parameters and that do not require any complex initialization logic.


If a class or struct has a large number of constructor parameters or requires complex initialization logic, then it may be better to use a traditional constructor.


Examples of using Primary Constructors

Here are some examples of how to use primary constructors in C#:


Creating a class with simple properties:

public class Person(string firstName, string lastName) 
{     
    public string FirstName { get; }     
    public string LastName { get; } 
} 

When to use: Ideal for classes where you want to keep it simple and directly initialize properties without additional logic.


Creating a class with dependency injection:

public class Logger(ILogger logger) 
{     
    private readonly ILogger logger;      
    public Logger(ILogger logger)     
    {         
        this.logger = logger;     
    }      
    
    public void Log(string message)     
    {         
        this.logger.Log(message);     
    } 
} 

When to use: Useful when your class requires external dependencies, commonly used in scenarios like dependency injection frameworks.


Creating a struct with immutable fields:

public struct Point(double x, double y) 
{     
    public double X { get; }     
    public double Y { get; } 
} 

When to use: Suitable for situations where immutability is crucial, such as when creating lightweight, value-like structures.


The above examples illustrate the versatility of primary constructors in handling different requirements, from straightforward property initialization to managing dependencies and ensuring immutability. Choosing the right approach depends on the specific needs of your code and the characteristics you want to enforce in your classes or structures.


Common Mistakes and Pitfalls when Using Primary Constructors in C#

Primary constructors in C# 12 are a powerful feature, but they come with their own set of potential pitfalls.


Here are some common mistakes and tips on how to avoid them:

  1. Misunderstanding Scope: Primary constructor parameters are in scope throughout the class definition. This is different from other constructors where parameters are only in scope within the constructor method. So, it’s important to remember that these parameters are available anywhere in the body of the type.

  2. Incorrect Access: Primary constructor parameters aren’t members of the class. For example, a primary constructor parameter named param can’t be accessed as this.param. This is a common mistake when developers are used to accessing member variables with this.variable.

  3. Forgetting Assignment: Primary constructor parameters can be assigned to. This is similar to other constructors where parameters can also be assigned to member variables. However, developers sometimes forget to assign these parameters, leading to uninitialized variables.

  4. Overlooking Properties: Primary constructor parameters don’t become properties, except in record types. This is different from other constructors where parameters are typically used to initialize properties. Developers need to be aware of this difference to avoid unexpected behavior.

  5. Neglecting Constructor Overloads: Once you’ve declared a primary constructor, your class will no longer have an implicit parameterless constructor. You can always add constructor overloads with one condition: they must call the primary constructor using the this() keyword. Forgetting to do this can lead to errors.


Conclusion

Our exploration of the C# 12 Primary Constructor reveals a solution to the challenges of older constructors. This feature simplifies code, eliminates redundancy, and enhances readability. We've learned its definition, concise syntax, and practical applications—from simple property initialization to handling dependencies and ensuring immutability.

bottom of page