On a recent LinkedIn Post from Saeed Esmaeelinejad, he asked what is the outcome of:
bool flag = SomeMethod();
M(flag ? 1 : 2);
void M(long number) => Console.WriteLine("long");
void M(short number) => Console.WriteLine("short");
What is the outcome?
How does overload resolution work in C#?
C# basically checks two things to find a suitable method:
- Find an exact match for the type of arguments.
- If it can't find that, try to find a match with implicit conversions
In the case of Saeed's question, the first step fails, because the type of the argument is int
, and there is no method with an int
argument. So the second step is tried, and there is an implicit conversion from int
to long
and short
. So both methods are applicable. But which one is better?
The answer here is simple: Imagine the number 300000
. This number can be converted to a long
without any loss of information, but it can't be converted to a short
without losing information. So the long
method is better, and that is the one that is called. So the answer for Saeed's question is long
.
But wait, we are not done here! Let's change the code a little bit:
const int a = 1;
M(a);
void M(long number) => Console.WriteLine("long");
void M(short number) => Console.WriteLine("short");
What is now the outcome? Well the picture shifts: Now the short
method is better, because the number 1
can be converted to a short
without losing information, and the compiler can determine this at compile time as a
can never change! We can also take this one step further:
const int a = 1234567;
var b = 1;
M(a);
M(b);
In both cases, we get long
as the answer. Huhh? Why? Well, the compiler can't determine the value of b
at compile time, so it can't determine if the conversion from int
to short
will lose information. So it has to assume that it will lose information, and therefore the long
method is better.
Let's consider a last example that might seem surprising:
var flag = false;
M(flag switch { true => 1, false => 2 });
What do you think is the outcome, short
or long
? Surprisingly it is short
in contrast to the initial question. Why is that? Well, switch
expressions are different than the ternary operator as switch
statements are determined at compile-time. So the possible outcomes are known in advance here.
While we are at it
Going away from the original question, I have some more interesting parts for you. Have a look at the following code sample:
void M(int number) => Console.WriteLine("int");
void M(int number, int anotherNumber = 0) => Console.WriteLine("int with optional");
M(1);
If we call M(1)
, both methods seem to be suitable, but in this case, the one without the optional parameter is prioritized over the one with. Therefore we print out "int" to the console.
One last example to finish the topic:
void M(object o) => Console.WriteLine("object");
void M(double d) => Console.WriteLine("double");
void M(float f) => Console.WriteLine("float");
M(5);
The answer might be surprising: It's float
as result. You can see this on sharplab.io in action. In this example, you can see that the lowered code is M(5f);
, so the compiler decided that float
is the most precise type. That makes sense as it "occupies" the same memory space and still is able to represent the number.
Conclusion
Overload resolution in C# can be fun! At least, I liked the small riddle of Saeed a lot and it led me to dig into some C# code specifications to understand the behavior of the language!