Overload resolution in C#

7/8/2023

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:

  1. Find an exact match for the type of arguments.
  2. 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!

Upcoming .NET User Forum Zurich / 6th July 2021

I am looking forward to give a talk about some insights and pitfalls of async / await.

I will talk about the differences between asynchronous and parallel programming. Also a brief outlook how the state machine internally works. Feel free to join here: https://www.meetup.com/dotnet-zurich/events/278916769/

Performance (ReadOnly)List vs Immutable collection types

A bit back on LinkedIn, there was a discussion about read-only collection and immutability where this is not the point I want to discuss now, as I already covered that here: "ReadOnlyCollection is not an immutable collection".

This post is just about the performance of those types compared to our baseline, the good old List<T>. It also explains why we see the results we see.

How not to benchmark!

I came across a recent LinkedIn post about the let statement in LINQ and it's performance implication. And in typically influencer fashion it out right claimed that using let in LINQ is a bad idea and should be avoided. But is it a bad idea?

An error has occurred. This application may no longer respond until reloaded. Reload x