Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Breaking change in method overload resolution in C# 6 - explanation?

We've recently moved from VS2013 to VS2017 in our company. After the upgrade our codebase would no longer build. We would get the following error:

The call is ambiguous between the following methods or properties: 'IRepository<T>.Get(object, params Expression<Func<T, object>>[])' and 'IRepository<T>.Get(object, params string[])'

Here is the call itself:

this.mainRepository.Get(newEntity.Id);

...and the interface definition:

public interface IRepository<T> where T : class
{
    T Get(object id, params Expression<Func<T, object>>[] includeExprs);
    T Get(object id, params string[] includeExprs);
}

I was wondering if anyone here could explain why this is the case. I suspect the new Improved method overload resolution feature of C# 6.0 but looking at the language spec I wasn't able to find out the exact rule responsible for the issue.

EDIT

I wrote a follow-up blog post about this problem: http://codewithstyle.info/method-overload-resolution-in-c-6-0-an-interesting-bug-story

like image 509
milosz Avatar asked Mar 22 '17 12:03

milosz


1 Answers

I discovered the same thing when upgrading to Visual Studio 2015 so this is not new with 2017, but it is new since 2013.

I reported it on github here:

Code that compiles in VS2013 fails with CS0121 in 2015; overloads with different params parameter types #4458:

The problem is that the code is ambiguous and the new Roslyn compiler is stricter on this than the previous compiler.

The issue was closed with an action to change the documentation instead of reverting to the old behavior, as part of issue Add information about #4458 to "Overload Resolution.md" #4922.

In particular, AlekseyTs commented this:

In the interest of the future health of our overload resolution code, we decided to keep the breaking (and correct) behavior. If we get more than this single case, we may want to reevaluate.

So there you have it. The new compiler is stricter on this and you need to change your code.

Given the comment above by AlekseyTs, you might want to consider reporting this to Microsoft on github as an additional such case. If this kind of problem is becoming more widespread now that 2017 is out, because a lot of people/companies have waited with upgrading, as the comment say they may want to reevaluate.

In addition, the reason why you don't find anything in the (older) documentation about this is that this was a "hidden feature" of the older compiler, as evident from the change they did to the documentation:

The old compiler implemented special rules for overload resolution (not in the language specification) in the presence of unused param-array parameters, and Roslyn's more strict interpretation of the specification (now fixed) prevented some programs from compiling.

(my emphasis)


When we fixed the same type of problem in our code we ended up with something like this (example using your code):

public interface IRepository<T> where T : class
{
    T Get(object id, Expression<Func<T, object>>[] tieBreaker, params Expression<Func<T, object>>[] includeExprs);
    T Get(object id, string tieBreaker, params string[] includeExprs);
}

notice the addition of the two tieBreaker parameters

Then we just included the explicit parameter into the collection with the others inside. If you need to be able to call the method with none of those optional extra parameters you should add a 3rd overload that doesn't have them to be explicit about which overload should be called so your final interface might look like this:

public interface IRepository<T> where T : class
{
    T Get(object id);
    T Get(object id, Expression<Func<T, object>>[] tieBreaker, params Expression<Func<T, object>>[] includeExprs);
    T Get(object id, string tieBreaker, params string[] includeExprs);
}
like image 113
Lasse V. Karlsen Avatar answered Oct 23 '22 17:10

Lasse V. Karlsen