3

Here I have a simple scenario with two methods where I get an ambiguous invocation from one another:

This is a my code:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsNoSubsYpdcResult, bool> predicate = null)
    => predicate == null
        ? Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status))
        : Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status).Where(predicate));

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate = null)
    => predicate == null
        ? GetJobsViewModels(vesselId, status)
        : GetJobsViewModels(vesselId, status).Where(predicate);

I dont like changing the names of methods but from the second one I get the error message:

Ambiguous Invocation

I would like to call first one from second one like I am trying to do, does anybody know how can I do this without changing the method names otherwise I am supposed to change them ?

brijber
  • 711
  • 5
  • 17
Rey
  • 3,663
  • 3
  • 32
  • 55
  • 1
    Please show the call that gave you that error. – Lasse V. Karlsen Jun 21 '17 at 10:28
  • @LasseV.Karlsen: I *think* it's already there. The body of the second expression contains calls to the depicted methods. – O. R. Mapper Jun 21 '17 at 10:29
  • 3
    Well both methods are completely the same if you skip the optional argument, how should the compiler know? – Icepickle Jun 21 '17 at 10:29
  • Yes I know that but I am looking for a work-around to handle this if there is any – Rey Jun 21 '17 at 10:29
  • Please check if this isn't a duplicate of [Breaking change in method overload resolution in C# 6 - explanation?](https://stackoverflow.com/questions/42951282/breaking-change-in-method-overload-resolution-in-c-sharp-6-explanation/42951548#42951548). – Lasse V. Karlsen Jun 21 '17 at 10:30
  • I assume you could pass an always true `Func` that you declare somewhere, though it might be simpler to either add a generic parameter to your methods or to give them distinct names – Icepickle Jun 21 '17 at 10:30
  • You can try explicitly typing null: `GetJobsViewModels(vesselId, status, (Func)null)` – Lasse V. Karlsen Jun 21 '17 at 10:31
  • 2
    Or, you can simply remove the default value for the overload you *don't* want it to call if you ommit it, likely the second one. In other words, make the second overload only callable if you actually specify the delegate. – Lasse V. Karlsen Jun 21 '17 at 10:34
  • 1
    Lasse is perfectly on spot. Without a predicate the 2nd overload is only forwarding to the first overload - so there is really no need to ever call the 2nd without a predicate and so it doesn't need the default value. – grek40 Jun 21 '17 at 10:36

4 Answers4

4

The best way to handle this is to not have two overloads that are ambiguous, that way you don't need to remember how to special-case this everywhere you call them.

Since your second overload calls the first I would say the most logical cause of action would be to remove the default parameter value for that delegate:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate)
    => predicate == null
        ? GetJobsViewModels(vesselId, status)
        : GetJobsViewModels(vesselId, status).Where(predicate);

You should also reason about the ability to call this method specifying the delegate parameter, but explicitly specifying null. If this is not really meant to be possible anyway (ie. the first part of the ternary expression is not really going to be used) then I would simplify the entire method:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsViewModel, bool> predicate)
    => GetJobsViewModels(vesselId, status).Where(predicate);

Now, having said all that I don't really see the point of this second method anyway, as on the outside I could simply tuck on a .Where(...) filter anyway. Since the method doesn't do anything clever with this predicate, like passing it on and doing it on a lower level, or optimizing the way it fetches data, then this is really just syntactic sugar.

However, that's just my opinion, and I admittedly don't know how all of this is going to be used. My real answer is the first part though, you really should avoid declaring ambiguous methods.


Also see this question: Breaking change in method overload resolution in C# 6 - explanation?.

Overload resolution changed when Roslyn entered the scene and they decided to document the change and make it breaking instead of reintroducing the old "bug".

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • "I don't really see the point of this second method anyway, as on the outside I could simply tuck on a `.Where(...)`" - well, it *might* have its uses if the OP needs to provide a method with a particular signature that takes a predicate (because outside code will call it like that via a delegate). – O. R. Mapper Jun 21 '17 at 10:41
  • I was thinking to do so but the real problems comes to the properties both models have. `JobsViewModel` is so different from `JobsViewModel` but behind the scenes the AutoMapper is doing its job to supply `JobsViewModel` model properties from `JobsViewModel` model properties so in short terms I need both of them in different cases. – Rey Jun 21 '17 at 10:41
  • 1
    Yep, clarified the part of "not being needed", the answer is really the first part, don't declare ambiguous methods. – Lasse V. Karlsen Jun 21 '17 at 10:43
3

You need to provide the optional argument and make its type clear to the compiler, even if the value is null:

GetJobsViewModels(vesselId, status, (Func<JobsNoSubsYpdcResult, bool>)null)

EDIT: In the particular case presented by the OP, Lasse's solution is preferable.

The solution shown in this answer here, on the other hand, can be applied generally, also when disambiguating overloads where the other overload does not have an optional parameter that can be made non-optional (e.g. when invoking some of the ArgumentNullException constructors).

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
2

The only way to do this would be to fill in the optional parameter with a value of the appropriate type, so that the compiler knows which overload to pick. For example:

public IEnumerable<JobsViewModel> GetJobsViewModels(
    Guid vesselId,
    int status,
    Func<JobsViewModel, bool> predicate = null)
{
    // We're never filtering by JobsNoSubsYpdcResult, but this
    // satisfies overload resolution
    Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
    return predicate == null
        ? GetJobsViewModels(vesselId, status, resultPredicate)
        : GetJobsViewModels(vesselId, status, resultPredicate).Where(predicate);
}

Or to avoid repetition:

public IEnumerable<JobsViewModel> GetJobsViewModels(
    Guid vesselId,
    int status,
    Func<JobsViewModel, bool> predicate = null)
{
    // We're never filtering by JobsNoSubsYpdcResult, but this
    // satisfies overload resolution
    Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
    var allResults = GetJobsViewModels(vesselId, status, resultPredicate);
    return predicate == null
        ? allResults : allResults.Where(predicate);
}

Or to avoid even more repetition, introduce your own extension method:

public static IEnumerable<T> OptionalWhere<T>(
    this IEnumerable<T> source,
    Func<T, bool> predicate) =>
    predicate == null ? source : source.Where(predicate);

Then you can rewrite both methods to use that:

public IEnumerable<JobsViewModel> GetJobsViewModels(Guid vesselId, int status, Func<JobsNoSubsYpdcResult, bool> predicate = null) =>
    Mapper.Map<IEnumerable<JobsViewModel>>(_procedureService.Tech_GetJobsNoSubsYPDC(vesselId, status))
        .OptionalWhere(predicate);

public IEnumerable<JobsViewModel> GetJobsViewModels(
    Guid vesselId,
    int status,
    Func<JobsViewModel, bool> predicate = null)
{
    // We're never filtering by JobsNoSubsYpdcResult, but this
    // satisfies overload resolution
    Func<JobsNoSubsYpdcResult, bool> resultPredicate = null;
    return GetJobsViewModels(vesselId, status, resultPredicate)
        .OptionalWhere(predicate);
}

(You can use an expression-bodied method if you're happy to cast null to Func<JobsNoSubsYpdcResult, bool> or use a static field for that.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thanks a lot @Jon Skeet for solving repetition problems. That will help me in many other cases with the extension method(one thing - make sure it is a generic method :p) – Rey Jun 21 '17 at 10:53
  • @RajmondBurgaj: Whoops, have added the type parameter. – Jon Skeet Jun 21 '17 at 11:03
  • if I make the same generic method but with IQueryable does it materialize the data first or the where condition is applied to the database ? – Rey Jun 21 '17 at 13:34
  • @RajmondBurgaj: You'd need to take `Expression>` instead of `Func` but then it should be okay. – Jon Skeet Jun 21 '17 at 13:40
1

If you really want what you say you want, you need to pass the proper predicate argument, e.g. GetJobsViewModels(vesselId, status, new Func<JobsNoSubsYpdcResult, bool>( x => true ));

brijber
  • 711
  • 5
  • 17