.NET Tools How-To's

Required Keyword, Checked Operators, nameof Operator Scope – Using C# 11 in Rider and ReSharper

ReSharper and Rider support for C# 11

The .NET 7 SDK arrived a few months ago, with many .NET developers looking forward to this release and the brand-new C# language features that come along with it. If you haven’t put your fingers on it yet, all you need is:

  • Download the latest .NET SDK
  • Update your global.json if you have one
  • Update the TargetFramework in your project to net7.0 (only for some features)
  • Update the LangVersion property in your project to 11.0 or preview

In this series, we will dive into the most interesting features that are coming with C# 11 and show how we updated ReSharper and Rider to support you in applying them to your codebase with ease:

This third post will walk you through the required keyword, checked operators, and the extended nameof operator scope.

Required Keyword

Until recently, when defining data holder types (DTOs/POCOs), you had to make a choice between using constructor initialization or object initializers. Unfortunately, both of these styles imply certain drawbacks.

The conventional constructor initialization is rather verbose. When creating an object, you have to pass arguments positionally, which can feel arbitrary and isn’t great to read either (unless you use argument names or inlay hints):

var person = new Person(firstName: "Pete", lastName: "Mitchell");

class Person
{
    public string FirstName { get; }
    public string LastName { get; }

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

The alternative object initialization was improved through the introduction of the init keyword, which makes sure that properties can’t be mutated after initialization. However, it still lacks the ability to express that properties are required to be set:

var person = new Person { FirstName = "Bradley", LastName = "Bradshaw" };

class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

C# 11 addresses this problem for object initializers by introducing the required keyword for fields and properties. Once a member is declared as required, the compiler will issue an error when the member is not set at creation:

var person = new Person { FirstName = "Tom", LastName = "Kazansky" };

// error: FirstName and LastName are not set
// var p = new Person();

class Person
{
    public required string FirstName;
    public required string LastName { get; init; }
}

ReSharper and Rider 2022.2 fully support the required keyword. It is recognized in your code and listed in code completion. Furthermore, they provide all the necessary code analysis around this feature (i.e., to show errors) as well as quick-fixes to initialize the required fields and properties:

Fix missing Object Initialization

Checked Operators

For operations over integral numeric types, C# always allowed you to generate arithmetic overflow checks. You can achieve this by setting the CheckForOverflowUnderflow compiler option for the whole project or individually via checked/unchecked blocks and expressions in your code.

Unfortunately, this feature was limited to built-in integral numeric types. User-defined number-like types (e.g., vectors and matrices) could not define dedicated operators to perform overflow checks. This becomes even more problematic with the introduction of generic math support.

To mitigate this issue, C# 11 introduces the notion of checked operators, that you can declare next to your regular operators. Depending on whether you’re in a checked or unchecked context, the appropriate method is called:

// Called in unchecked (and previously checked) context
public static InVector operator +(IntVector a, IntVector b)
    => new(a.X + b.X, a.Y + b.Y);

// Called in checked context
public static InVector operator checked +(IntVector a, IntVector b)
    => new(checked(a.X + b.X), checked(a.Y + b.Y));

ReSharper and Rider can handle the checked operator new syntax and also allow you to navigate from operator declarations to their usages and vice versa:

Navigating to Checked Operator declaration and usages

For your already defined operators, you can use the Create matching checked operator quick-fix to create their counterparts:

Create matching Checked Operator

Nameof Operator Scope

The nameof operator was introduced in C# 6, allowing you to write code that is automatically checked by the compiler and much easier to refactor. Consider the following example:

static void Write(string property, string[] values)
{
    if (property.IsNullOrWhiteSpace())
        throw new ArgumentException("Value is empty", nameof(property));

    // ...
}

Naturally, once you rename the property parameter, the usage in the exception is also updated. But as we all know, many codebases have grown over time and still use simple string references in a number of places, which could potentially become out-of-sync. That is most certainly the case when you were referencing parameters in attributes, such as with the CallerArgumentExpressionAttribute that was introduced in C# 10 to capture the expression of another parameter as a string:

void OnlyWhen(
    Func<bool> condition,
    // [CallerArgumentExpression("condition")]    // Before C# 11
    [CallerArgumentExpression(nameof(condition))] // With C# 11
    string? conditionExpression = null)
{
}

ReSharper and Rider now allow you to reference parameters in attributes according to C# 11, and they also introduce a new context action – Capture constant/parameter name – that helps you convert old string references to use the nameof operator. Most importantly, you can invoke this from any string that references a member/parameter and choose to apply it to the whole solution:

Capturing constant or parameter names

Conclusion

The required keyword nicely solves the problem of choosing between conciseness (object initialization) or immutability (constructor initialization). With checked operators, you can make your code more robust and up-to-date with built-in types. Plenty of magic strings may be eliminated by utilizing the extended nameof operator scope.

Let us know in the comments if you have more ideas for additional features in ReSharper and Rider. And stay tuned for the next blog post, where we will take a look at auto-default structs, generic attributes, and default implementations for static interface members!

image description

Discover more