.NET Tools
Essential productivity kit for .NET and game developers
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:
- Escape Character, Extract Common Code, Params Modifier, Out Vars
- Equality Analysis, Ref Structs, Culture Previews, Using Directives
- Cast Expressions, Primary Constructors, Collection Expressions, List Patterns
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;
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;
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:
Probably much more often, you’ll see upcasts happening inside lambda expressions bodies, like in the following LINQ query:
Also, in generic methods and their arguments, you can convert to safer type annotations:
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;
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:
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:
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:
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:
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:
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:
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[]
):
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:
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:
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.