-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: OverloadResolutionPriorityAttribute
#102173
Comments
Tagging subscribers to this area: @dotnet/area-system-runtime-compilerservices |
Looks good as proposed. namespace System.Runtime.CompilerServices;
[AttributeUsage(
AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property,
AllowMultiple = false,
Inherited = false)]
public sealed class OverloadResolutionPriorityAttribute(int priority)
{
public int Priority => priority;
} |
* Add OverloadResolutionPriorityAttribute Closes #102173. * Base types * Switch from primary ctor to regular ctor, add docs.
And how can you understand what method is being invoked there? Now it's clear that if it's int[], then there will be a method with int[] and so on. And then what happens, I call the method Get(int[] array) with an int[] array, but Get(IList array) is invoked. Do have to guess which method will be called? Doesn't that violate SOLID principles? |
@Mr0N you'll understand it in exactly the same way you do today: by looking at quick-info and seeing what was chosen, or by looking at the compiled code and seeing what was emitted. I similarly don't understand how this would violate SOLID principles; the primary purpose of this attribute is to allow API authors who regret an API decision but can't fix it due to overloading rules and binary compat to fix those issues. |
Without knowing where this attribute is set in which method, I can't use the class. For example, if there's a DLL library with such a class, how am I supposed to know which method will be invoked? Essentially, I'm dependent on the internal implementation of the class. Dependency inversion principle |
No more than you already are? This attribute does not affect runtime behavior in anyway. It affects the compiler, and the impacts are completely visible at every call site it impacts. There's no silent, unexpected changes when using this feature. Can you give a specific example of what you're concerned by? |
I expect that Test(TestObject obj) will be called here, but Test(ITestObject obj) is being called instead. To understand which method overload will be invoked, I have to read the class code. So now I have to read every class before calling a method with the same types.
|
@Mr0N are you saying, that you know all overload resolution rules on the top of your mind and can currently always tell 100 % correctly which overload will be called by the compiler? Are you also concerned about this scenario:
Isn't that the same situation as with this proposed That happened already multiple times! The most interesting case of it I remember is when string interpolation handlers were added. I personally didn't care about it, because it made my code FASTER. Well, to be honest, in the beginning I had NO IDEA that was changed. |
if I pass an int[] array and the method signature is currently SetInfo(int[] array), I'm pretty sure this method will be called. If you add such an attribute, it will be very difficult to understand which method will be called. You would need to run a debugger to see which method was invoked, and even then, I don't know how you could figure out which method was called — the old one, the new one, or some extension method, and so on. This attribute will confuse everything. |
You would not, the IDE will happily show you the method that is being invoked. GoToDef will work, quick info will work, etc.
Can you tell me which overload is called here, using the existing rules from C# 1? using System;
var d = new Derived();
d.M(new int[] { 1, 2, 3 });
class Base
{
public void M(int[] s) => Console.WriteLine("Base");
}
class Derived : Base
{
public void M(ReadOnlySpan<int> a) => Console.WriteLine("Derived");
} Interpolated strings are another example where what you might think they call (the This API is a tool to allow developers to fix mistakes of the past. Yes, it can be abused, but lots of things in the language can be abused. The tooling has excellent support for giving you information about what your code is actually doing, whether you're in the IDE, the debugger, or even just reading IL. |
This example would not change behavior with OverloadResolutionPriority. There is no implicit conversion from |
You might also think you're calling a particular extension method, when in reality the compiler is pulling in a different one from a closer namespace. |
@Mr0N The point of the example is the usage of ReadOnlySpan. Your version uses arrays in both Base and Derived. |
As another example, imagine an overload that takes an object and another that takes a string, with an optional argument after. You pass a string. Which gets called? Fundamentally, you can't simply look at the API. You have to understand overload resolution. |
Well, it might be doing something like in that example, behavior, it adds the word 'new' itself.
|
If this is indeed the case, then it can be stated in the documentation that it is normal behavior for the 'new' operator to be added |
I'm not sure what you're using the |
Well, I mean, the compiler might add the word new by default, and because of this, it calls public void M(ReadOnlySpan a) instead of how it ideally should be public void M(int[] a) |
Well, it's just a guess. |
@Mr0N Regardless of that, the example that I showed does not require new, and in fact putting |
Well, okay then, maybe an attribute is really a good solution. It would be better if there was some attribute that fixes everything, so wouldn't have to use numbers in attribute |
Background and motivation
In order to support the C# proposed feature https://github.com/dotnet/csharplang/blob/d5bab90e846f9b3e9a7a4e552249643148f0194d/proposals/overload-resolution-priority.md, we add a new attribute to the BCL:
OverloadResolutionPriorityAttribute
. This feature allows applying the attribute to adjust the priority of an overload within a type. This will allow consumers like the rest of the BCL to adjust overload resolution for scenarios where a new, better API should be preferred (but won't be because overload resolution would prefer the existing one), or to resolve scenarios that will otherwise be ambiguous. One prime example isDebug.Assert
; with this API, we could adjustvoid Debug.Assert(bool condition, string? message)
to instead be[OverloadResolutionPriority(1)] Debug.Assert(bool condition, [CallerArgumentExpression(nameof(condition))] string? message = "")
. The real change here would be a bit more complex due to the number of overloads thatDebug.Assert
already has, but this is a simple enough example to convey they intent.API Proposal
API Usage
Alternative Designs
No response
Risks
One of the BCL's main uses of
OverloadResolutionPriorityAttribute
will be to effectively make source-breaking changes while maintaining binary compatibility. Care will have to be taken when using the attribute to ensure only the intended consequences occur. For a few of these cases, it can effectively make specific overloads impossible to call directly, and users would have to resort to using something like delegate conversion,UnsafeAccessor
, reflection, or manual IL.The text was updated successfully, but these errors were encountered: