Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

.NET Tools How-To's ReSharper Platform Rider

Cast Expressions, Primary Constructors, Collection Expressions, List Patterns – C# Language Support in 2024.2

Our release for ReSharper and Rider 2024.2 is just around the corner, and we have lots of exciting features shipping for the new C# 13 and current C# and VB.NET! Since there are so many, we will split them into multiple blog posts. So make sure to check those posts out as well!

In this series, we will cover features around:

Make sure to download the latest version of ReSharper or Rider to follow along:

Cast Expression Analysis

The C# language uses C-styled cast expressions, which can be static upcasts or dynamic downcasts. During refactorings, it is very easy to turn a static upcast into a dynamic one, thus opening the door for runtime errors:

interface IBase;
interface IDerived : IBase;
class Derived : IDerived;

var derivedInstance = new Derived();

// upcast: completely safe, no-op in MSIL and at a runtime
var baseReference = (IBase) derivedInstance;

// downcast: runtime cast, can throw InvalidCastException
var derivedReference = (IDerived) baseReference;
Copy to clipboard

In other words, there is no way to tell if a cast is a safe upcast or a runtime downcast without knowing the complete type hierarchy. What a bummer! However, in C#, it is often possible to use explicit type annotations instead of cast expressions. With these type annotations, the code will simply not compile if the types are not compatible:

// implicit upcast
IBase baseReference = derivedInstance;
Copy to clipboard

In 2024.2, we identified various possibilities to use explicit type annotations instead of fragile C-styled casts and implemented a set of new inspections and quick-fixes:

Replacing cast with explicit variable type
Replacing cast with explicit variable type

Probably much more often, you’ll see upcasts happening inside lambda expressions bodies, like in the following LINQ query:

Replacing cast with lambda return type
Replacing cast with lambda return type

Also, in generic methods and their arguments, you can convert to safer type annotations:

Replacing cast with type arguments
Replacing cast with type arguments

Primary Constructors

Introduced in C# 12 and inspired by F#, primary constructors for non-record types have achieved great adoption. Most notably, developers are happy to eliminate the duplications in field declarations and assignments. However, there’s also a frequently discussed downside. Primary constructor parameters cannot have readonly modifiers and, thus, are mutable throughout all instance members after initialization:

class Service(IDependency dependency)
{
    public void DoWork()
    {
        dependency.Use();
    }
    
    public void SetupHack()
    {
        dependency = new AdhocDependencyImpl();
        DoWork();
    }
}

interface IDependency;
class AdhocDependencyImpl : IDependency;
Copy to clipboard

In 2024.2, we are giving you more peace of mind by ensuring the immutability of captured primary constructor parameters through an inspection. Inspections about the mutations will occur not only at the parameter declarations but also on all mutations themselves. They also allow you to convert to an explicit field declaration through a quick-fix to make the mutations more obvious due to the lack of the readonly modifier:

Converting to explicit field declaration
Converting to explicit field declaration

Feel free to tweak this new inspection from the Alt-Enter menu. You can Disable once with comment to make an ad-hoc exception, Configure inspection severity to completely turn it off or even raise it to an error in more strict codebases:

Suppressing and configuring an inspection
Suppressing and configuring an inspection

Speaking of more strict codebases – some of you might want to disallow primary constructor captures to keep everything plain and explicit. For those needs, we have added a new inspection Primary constructor parameter capturing is disallowed with an accompanying quick-fix for explicit fields:

Converting from disallowed parameter capturing
Converting from disallowed parameter capturing

Note that this inspection is disabled by default. If you enable it in the settings dialog, make sure to choose the solution layer to let your team have the same view:

Primary constructor inspection configuration
Primary constructor inspection configuration

Collection Expressions

Since their introduction in .NET 8, we have gathered more feedback and real-world problems to improve our tooling for collection expressions. In 2024.2, we now consider collection interface types like IEnumerable<T> or IList<T> as well for your conversions:

Converting to collection expressions for interface types
Converting to collection expressions for interface types

Mindful readers might remember that we’ve intentionally avoided these conversions since they can break the callers. As a compromise, we are now allowing you to opt out of this behavior by disabling the Suggest when the target type is a collection interface type option:

Collection expression inspection configuration
Collection expression inspection configuration

Unfortunately, collection expressions are not universally applicable since they require a context where the target type is known. Therefore, you sometimes still need the older collection and array creation syntax that explicitly declares nominal types. In 2024.2, we’ve added two context actions, Convert to collection initializer (ReadOnlyList<T>) and Convert to array creation expression (T[]):

Converting to collections and arrays
Converting to collections and arrays

The 2024.2 release also allows you to convert when no target type is available. The context action then changes your declaration to an explicit target type:

Converting to collection expression for variables
Converting to collection expression for variables

Split List Patterns

List patterns are a powerful feature introduced in C# 11 to match collections against a desired shape and recursively match elements. One of the downsides is that you can only match against constant values. If you need to match against a runtime-dependent value, you’ll be forced to replace the list pattern with ordinary checks.

In 2024.2, we introduce a new Split list pattern context action to easily convert to ordinary checks, no matter how complex the list pattern is:

Splitting list patterns
Splitting list patterns

Conclusion

Give it a go with the latest ReSharper 2024.2 and Rider 2024.2, and let us know if you have any questions or suggestions in the comments section below.

image description