Understanding OverloadResolutionPriority attribute in C# 13
C# 13 adds support for a new attribute: [OverloadResolutionPriority]
. This attribute is niche, so most of you won't use it. However, it's a nice addition to the language, and it's worth knowing about it if you are writing libraries.
Let's consider this simple example:
Sample.Test(); // Print: Test 1
static class Sample
{
public static void Test() => Console.WriteLine("Test 1");
}
Now, you want to update the signature with a new optional parameter:
Sample.Test(); // Print: Test 1
static class Sample
{
// ❌ Binary breaking change as the method with no parameter doesn't exist anymore
public static void Test([CallerMemberName]string name = "") => Console.WriteLine("Test");
}
A better solution would be to add a new overload, but it doesn't work:
Sample.Test(); // Print: Test 1
static class Sample
{
public static void Test() => Console.WriteLine("Test 1");
// ❌ No more breaking change, but the new method is not called
public static void Test([CallerMemberName]string name = "")
=> Console.WriteLine("Test 2");
}
The application will write "Test 1" to the console. The reason is that the compiler prefers the overload without parameters over the one with a default parameter as it matches the call site better.
Note that removing the first overload would be a binary breaking change. This is something that you cannot do in a library. The new [OverloadResolutionPriority]
attribute allow to specify the priority of the overload resolution to the compiler. So, the compiler will choose the compatible overload with the highest priority.
Sample.Test(); // Print: Test 2
static class Sample
{
[OverloadResolutionPriority(-1)]
public static void Test() => Console.WriteLine("Test 1");
public static void Test([CallerMemberName]string name = "") => Console.WriteLine("Test 2");
}
Using the attribute, you can now add a new overload without breaking the existing code. The compiler will choose the overload with the highest priority!
You can also use this attribute to prioritize some overloads that would require implicit conversions:
Sample.Test(1); // Print: byte
Sample.Test(1000); // Print: int
static class Sample
{
public static void Test(int a) => Console.WriteLine("int");
[OverloadResolutionPriority(1)]
public static void Test(byte a) => Console.WriteLine("byte");
}
Note that [OverloadResolutionPriority] only supports methods in the same class. For instance, you cannot add a new extension method with higher priority than another extension method in another class.
Note that another use case for this attribute is to prioritize ReadOnlySpan<T>
over Span<T>
for extension methods. In the next version of C#, the compiler will consider Span as first-class citizen, allowing extension methods on ReadOnlySpan<T>
to be used by Span<T>
without needing an explicit conversion. So, if you have overloads that accept both Span<T>
and ReadOnlySpan<T>
, you can use the attribute to prioritize the ReadOnlySpan<T>
overload. This will help with JIT, inlining, and trimming. Here's an example: [API Proposal]: Apply [OverloadResolutionPriority] to Span-based overloads
static class Ex
{
public static void Sample<T>(this ReadOnlySpan<T> a) => Console.WriteLine("ReadOnlySpan");
// Avoid indirection, the compiler will call the other method directly
[OverloadResolutionPriority(-1)]
public static void Sample<T>(this Span<T> a) => Sample((ReadOnlySpan<T>)a);
}
#Polyfill
The attribute is available in .NET 9. If you are targeting a previous version of .NET and you need to use this attribute, you can use a polyfill. You can use an existing NuGet package such as Meziantou.Polyfill or create your own implementation:
namespace System.Runtime.CompilerServices;
/// <summary>
/// Specifies the priority of a member in overload resolution. When unspecified, the default priority is 0.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
internal sealed class OverloadResolutionPriorityAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="OverloadResolutionPriorityAttribute"/> class.
/// </summary>
/// <param name="priority">The priority of the attributed member. Higher numbers are prioritized, lower numbers are deprioritized. 0 is the default if no attribute is present.</param>
public OverloadResolutionPriorityAttribute(int priority)
{
Priority = priority;
}
/// <summary>
/// The priority of the member.
/// </summary>
public int Priority { get; }
}
#Further reading
Do you have a question or a suggestion about this post? Contact me!