2

Why does C#/Framework allow me to use an optional parameter to create an overload that it otherwise does not allow?

public static TOut? NullableConvert<TOut>(object source, Func<object, TOut> converter) where TOut : struct
{
}

public static TOut NullableConvert<TOut>(object source, Func<object, TOut> converter) where TOut : class
{
}

When I try above overloads I get the following error which I agree with:

Error CS0111 Type 'DataHelpers' already defines a member called 'NullableConvert' with the same parameter types


However, if I add an optional parameter to one of the methods as shown below then I am allowed to use these overloads (please note object x = null).

public static TOut? NullableConvert<TOut>(object source, Func<object, TOut> converter) where TOut : struct
{
}

public static TOut NullableConvert<TOut>(object source, Func<object, TOut> converter, object x = null) where TOut : class
{
}

When I run following the run time resolves correct overloads without the optional parameter

long? x = DataHelpers.NullableConvert(DBNull.Value, Convert.ToInt64);
string y = DataHelpers.NullableConvert(DBNull.Value, Convert.ToString);

How are the compiler/runtime able to resolve the overload without the optional parameter?

Why did I get the error at the first place if the methods could be resolved?

Menol
  • 1,230
  • 21
  • 35
  • 5
    Because an optional param is translated to a call with default-value on the calling-site when compiling, turning this into a call like `NullableConvert(DB.Value, Convert.ToInt64, null)`. – MakePeaceGreatAgain Mar 05 '20 at 10:52
  • Does this answer your question? https://stackoverflow.com/a/20705709/2169762 – Martin Mar 05 '20 at 10:52
  • 1
    As @HimBromBeere the grumpy bear says, there actually isn't such a thing as an optional parameter. It creates an overload with the actual parameter, and uses attributes to indicate the value that callers should provide, if it isn't provided in code. – CodeCaster Mar 05 '20 at 10:54
  • 1
    This is not specifically a .Net Core issue - it's a generic C# Language issue. – Matthew Watson Mar 05 '20 at 11:06
  • 1
    @HimBromBeere I agree. Where I'm baffled is how does the compiler know that I meant the method with the optional parameter when I used ```string y ....```? The optional parameter is not playing any role here. If the compiler can resolve that (which it certainly can) why did it give me the error at the first place? – Menol Mar 05 '20 at 11:06
  • 1
    @CodeCaster I think that the asker is aware of this, but it’s just begging the question. The issue remains the same: now there’s *one more overload* that only differs in its return value and generic constraint. – Konrad Rudolph Mar 05 '20 at 11:07
  • @Martin thanks. While that's a great explanation my current question is not related to the return type. – Menol Mar 05 '20 at 11:09
  • 2
    The interesting thing here is that if the compiler can tell the difference at the call site, why does the language not allow the overload without adding a dummy parameter? I know this comes down to the language specification, but it's a little odd. The compiler must be looking at the parameter types to decide at the call site (since you can remove the use of the return values altogether and it still works). – Matthew Watson Mar 05 '20 at 11:09
  • 1
    [Here's a simplified example with extraneous details removed](https://dotnetfiddle.net/XspCcm) (on DotNetFiddle) – Matthew Watson Mar 05 '20 at 11:22

2 Answers2

5

A method-overload is not allowed to only differ on the return-type (as well as the generic definition, just for completeness).

On the other hand all calls to a method having an optional argument are just compiled to a call with the with default-value on the calling-site. So in your case the compiler will turn this

NullableConvert(DB.Value, Convert.ToString)

into this:

NullableConvert(DB.Value, Convert.ToInt64, null)

while changing the method

public static TOut NullableConvert<TOut>(object source, Func<object, TOut> converter, object x = null) where TOut : class
{
}

into this:

public static TOut NullableConvert<TOut>(object source, Func<object, TOut> converter, object x) where TOut : class
{
}

However there´s no reason to also replace the method for the struct as well, because there is no optional parameter defined for that method. So in the IL there exist the following two methods:

public static TOut NullableConvert<TOut>(object source, Func<object, TOut> converter, object x) where TOut : class
{
}
public static TOut? NullableConvert<TOut>(object source, Func<object, TOut> converter) where TOut : struct
{
}

A call to NullableConvert(DB.Value, Convert.ToString) will therefor not be compiled to the forementioned overload, because there already is a perfect match for TOut: struct.

So in short before the compiler tries to resolve any overload, it replaces any method having optional parameters.

MakePeaceGreatAgain
  • 35,491
  • 6
  • 60
  • 111
  • 1
    You are correct, My question originates because we know return type doesn't qualify a different overload so when I called the second overload ```string y ....``` without any indication about the optional parameter. How did the compiler know? – Menol Mar 05 '20 at 11:15
  • 1
    Because the method having optional parameter is replaced before resolving any calls. So at the time the compiler tries to get the right overload, there exist only the two methods mentioned above: one for `TOut: class` having `object` as **not optional** argument, and one for `TOut: struct` having no third argument at all. – MakePeaceGreatAgain Mar 05 '20 at 11:18
  • 1
    @HimBromBeere Unfortunately that explanation doesn’t really make sense because, in order for the compiler to fill in the optional argument (and thus create a three-argument method), it *first* has to perform overload resolution to figure out that we’re targeting the method with an optional third parameter, rather than the one having only two parameters In your answer you’re effectively glossing over that the very first step requires overload resolution to have already happened. – Konrad Rudolph Mar 05 '20 at 11:37
  • @KonradRudolph Do you mean that after creating the method with 3 args, the compiler has to get those statements in calling-site that have to be replaced as well? – MakePeaceGreatAgain Mar 05 '20 at 11:49
  • @HimBromBeere to make the question more related to your explanation, how does the compiler know the second call (```string y=...```) should go the ```NullableConvert(DB.Value, Convert.ToInt64, null)``` method and not the overload for structs? Bare in mind it was complaining before it couldn't make that decision using given hints (e.g. type constraints, etc...) – Menol Mar 05 '20 at 11:59
1

The return value is not part of the signature of a method. as already answered here

And also in the microsoft docs

Manfred Wippel
  • 1,946
  • 1
  • 15
  • 14
  • 3
    That explains the first half of the question, but not the actual question (which is about optional parameters). – CodeCaster Mar 05 '20 at 10:54
  • You are correct, My question originates because we know return type doesn't qualify a different overload so when I called the second overload ```string y ....``` without any indication about the optional parameter. How did the compiler know? – Menol Mar 05 '20 at 11:19
  • @Menol because in method calls with optional parameters are filled with the default value specified like HimBromBeere mentioned in his post. – Manfred Wippel Mar 05 '20 at 15:00