.NET Tools
Essential productivity kit for .NET and game developers
Equality Analysis, Ref Structs, Culture Previews, Using Directives – 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 follow along:
Enhanced Equality Analysis
Struct types have been available since the very first .NET Framework. If you’re taking advantage of them, you may have heard that it’s good practice to override Equals
and GetHashCode
for performance reasons, because the default runtime implementation suffers from boxing and reflection overhead. This is particularly relevant in hot paths that call equality members. Furthermore, the default implementation of GetHashCode
works in a way that it takes the hash of the first non-null reference type field, which may lead to a bad distribution.
In 2024.2, we are introducing a set of new inspections to identify such performance issues where equality is checked under the hood. You can fix these issues by generating equality members, or by turning it into a record struct
and letting the compiler generate implementations for you:
We have also added a new attribute [DefaultEqualityUsage]
to our JetBrains Annotations NuGet package, which allows you to mark types or parameters as being used for equality checks:
public class Equality
{
object N() => M(new MyPoint()); // warning
object M<T>([DefaultEqualityUsage] T obj) => null;
// no Equals/GetHashCode overrides
public struct MyPoint;
}
As part of this feature, we also annotated the BCL with the new [DefaultEqualityUsage]
attribute and discovered surprising details about some APIs. For example, you might assume that in a ConcurrentDictionary
the equality is only checked for the TKey
type. That is true until you use AddOrUpdate
, which can also check TValue
for being equal on update:
var idToData = new ConcurrentDictionary<int, Data>();
// ConcurrentDictionary<TKey, TValue>.AddOrUpdate implementation
// actually checks TValue for being equal on update:
idToData.AddOrUpdate(
key: id,
// compute what to add
addValueFactory: static id => default,
// do the update
updateValueFactory: static (id, data) => data);
// no Equals/GetHashCode overrides
public struct Data
{
// members
}
Last but not least, our analysis also catches suspicious non-structurally comparable types in record
types. In the following example, two Payload
objects would only be considered equal if they share the same byte[]
array reference – this is very likely not the intended behavior. You would rather call SequenceEqual
in your custom equality members:
Note that this inspection will only be checked if the record
type is being used in equality-relevant APIs in your solution.
Ref Struct Interfaces
The introduction of ref struct
types in C# 7.2 (Span<T>
and ReadOnlySpan<T>
) has enabled many new high-performance scenarios. Those benefits came with limitations in a few aspects though. They could not be boxed, could not appear as fields in reference types, could not be stored as array elements, and did not allow interface inheritance. Some limitations are by design, but some could be relaxed during language evolution. One of those relaxations is interface inheritance.
In the following example, we’ve got a generic algorithm PrintArrayLike
to print all items of an object of the type IArrayLike
. With C# 13, we can make our ref struct ArrayLikeSpan
implement that interface. However, invoking the method on that object is still not possible yet! Can you guess why?
var obj = new ArrayLikeSpan([1, 2, 3]);
PrintArrayLike(obj); // error
void PrintArrayLike<TArrayLike>(TArrayLike arrayLike)
where TArrayLike : IArrayLike
{
for (var index = 0; index < arrayLike.Length; index++)
Console.WriteLine(arrayLike[index]);
}
public readonly ref struct ArrayLikeSpan(Span<int> span)
: IArrayLike // now allowed in C# 13!
{
private readonly Span<int> span = span; // can store `ref struct` inside `ref struct`
public int Length => span.Length;
public int this[int index] => span[index];
}
public interface IArrayLike
{
int Length { get; }
int this[int index] { get; }
}
In the method PrintArrayLike
, you could theoretically add the following line, which would essentially break the “no boxing” rule:
// cannot box ref struct!
var reference = (object)arrayLike;
Therefore, the compiler requires you to make an explicit decision that the generic method can handle ref struct
types and disallows boxing. This is achieved with the allows ref struct
anti-constraint, which you can easily add through a quick-fix from the error:
The allows ref struct
constraint will also show up in code completion with the shorthand aref
:
Culture Previews
Since ReSharper 8, we’ve been helping you choose the correct formatting specifiers in code completion. However, for types like DateTime
or DateOnly
, we could only guess which culture will eventually be set when your code executes and how ToString
renders, so we chose CultureInfo.Invariant
as the default.
With 2024.2, we are adding a new tooltip that shows how a value would render in different popular cultures, including the current OS culture:
Sorting Using Directives
Are you in the team of developers that just feels good about their using
directives at the top of the file to be minimal and in order? In ReSharper and Rider, we are making sure that all file modifications make use of the same internal API which ensures exactly that. You may also know, that our code cleanup profiles have an option to optimize using
directives on a shortcut or save operation. Despite all this effort, files can still end up with unordered and redundant lists of imports, mainly explained by manual edits like merge operations.
In 2024.2, we are making it complete by adding a Sort ‘using’ directives context action that can be triggered independently from all other cleanup steps:
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.