Using Primary Constructors in C# 12
Tags: csharp, dotnet
Published: 2023-11-15 14:26:41

---
title: Using Primary Constructors in C# 12
published: true
description: C# 12 provides a new way to use constructors that can potentially save you time, but there are several things to watch out for.
tags: csharp, dotnet
cover_image: https://res.cloudinary.com/dk3rdh3yo/image/upload/w_526,dpr_auto,f_auto/v1699487654/blog/primary-constructors-in-csharp-12-dotnet/neon_blocks_zwe36p.webp
published_at: 11/15/2023
canonical_url: https://baldbeardedbuilder.com/blog/primary-constructors-in-csharp-12-dotnet/
---

C#’s new non-record class and struct primary constructors can make your code cleaner and more concise, but if you’re not careful, they’ll ruin your app. Let’s talk about what they are, why you might want to start using them, and how to avoid the problems they can bring.

## How Constructors Started

Let's start with a constructor that's familiar:

```csharp
public class GeoPoint
{
  private decimal _latitude;
  private decimal _longitude;

  public GeoPoint(decimal latitude, decimal longitude)
  {
    _latitude = latitude;
    _longitude = longitude;
  }
}
```

Our `GeoPoint` class is constructed with two parameters that are used to set two private properties on the class. Of course, another common use case is dependency injection, where we inject an instance of a class to separate the concerns of this class from others.

```csharp
public class AuthService
{
  private readonly UserRepository _users;

  public AuthService(UserRepository repository)
  {
    _users = repository
  }

  // ...
}
```

Regardless of the use case, the pattern is the same and it's what we've been writing in C# forever. But now primary constructors aim to simplify this code while also providing some interesting usage options.

## How Primary Constructors Work

Let's take a look at our `GeoPoint` class refactored to use primary constructors in C# 12.

```csharp
public class GeoPoint(decimal latitude, decimal longitude) { }
```

The constructor method has been removed and its parameters have been added to the class declaration line. Already, you can see a reduction in lines of code, but what really makes primary constructors powerful is that the parameters they specify are in scope throughout the declaring type's entire body.

### Parameter Scope

In this code snippet, we extend the `GeoPoint` to include expression-bodied members that return the hemisphere and meridian of the object. Notice the parameters are referenced like private properties of the class. That's because they are essentially private properties. They aren't available publicly but can be passed to base constructors, used to set properties, or even used in functions or expression-bodied members.

```csharp
public class GeoPoint(decimal latitude, decimal longitude)
{
  public Hemisphere => latitude >= 0 ? 'N' : 'S';
  public Meridian => longitude >= 0 ? 'E' : 'W';
}
```

This also means that our dependency-injected parameters look much cleaner as we don't have to explicitly define private properties.

```csharp
public class AuthService(UserRepository users)
{
  public bool IsLoggedIn(string username)
  {
    var user = users.GetUser(username);

    // ...
  }
}
```

### Constructor Overloads

One thing you'll figure out quickly is once you've declared a primary constructor, your class will no longer have an implicit parameterless constructor. But fear not, you can always add constructor overloads with one condition: they must call the primary constructor using the `this` keyword.

```csharp
public class GeoPoint(decimal latitude, decimal longitude)
{
  // Parameterless constructor with default values
  public GeoPoint(): this(0, 0) { }

  // Stay on the equator
  public GeoPoint(decimal longitude): this(0, longitude) { }

  public Hemisphere => latitude >= 0 ? 'N' : 'S';
  public Meridian => longitude >= 0 ? 'E' : 'W';
}
```

> Any additional constructors **must** call the primary constructor using the `this` keyword.

## Behavior of Note

If you're like me, you're already thinking about how you can refactor old code to make use of this cleaner syntax, but there are a couple things you need to keep in mind before you start.

### Naming Conflicts

Primary constructor parameter naming conflicts are allowed by the compiler with both fields and properties. When it occurs, the property or field will hide the primary constructor parameter. The only exception to that rule is property initialization.

```csharp
public class GeoPoint(decimal latitude, decimal longitude)
{
  decimal latitude;

  // Uses the primary constructor to initialize the property.
  public decimal Latitude { get; set; } = latitude;

  // Uses the field rather than the primary constructor.
  public Hemisphere => latitude >= 0 ? 'N' : 'S';
}
```

### Different Than Record Primary Constructors

Unlike record types, public properties are not generated and because of this, the `with` keyword cannot be used. Those using record types will want to account for this by explicitly declaring properties if they're needed.

```csharp
public record GeoPointRecord(decimal latitude, decimal longitude) { }
public class GeoPointClass(decimal latitude, decimal longitude) { }

var point1 = new GeoPointRecord(50, 230);
var point2 = point1 with { Latitude = 100 };

var point3 = new GeoPointClass(50, 230);

// This line won't compile because no Latitude property exists.
var point4 = point3 with { Latitude = 100 };
```

## Wrap Up

The new class and struct primary constructors introduced in C# 12 are a welcome addition to our syntax and allow us to write cleaner & more concise code, but for those with experience using primary constructors with record types, you'll need to be aware of some key differences.