11

I have the following extensions class:

public static class MatcherExtensions
{
    public static ExecMatcher<T1, T2> Match<T1, T2>(this Tuple<T1, T2> item)
    {
        return new ExecMatcher<T1, T2>(item.Item1, item.Item2);
    }

    public static ExecMatcher<T1, T2> Match<T1, T2>(this ITupleMatchable<T1, T2> item)
    {
        var tuple = item.PropertiesToMatch;
        return new ExecMatcher<T1, T2>(tuple.Item1, tuple.Item2);
    }

    public static ExecMatcher<T> Match<T>(this T item) { return new ExecMatcher<T>(item); }
}

If I create a tuple and invoke Match(), it correctly uses the first extension method:

var tuple = Tuple.Create(1, "a");
tuple.Match().With(1, "a")...   // compiles just fine.

If I create a int and invoke Match(), it correctly uses the last extension method:

var one = 1;
one.Match().With(1)... // compiles just fine.

However if I create SomeClass, which implements ITupleMatchable<int, string> and try and match on it, the compiler still chooses the third extension method, rather than the second one, despite the latter being a more specific match:

var obj = new SomeClass(1, "a");
obj.Match().With(1, "a")... // won't compile.

Based on Eric Lippert's answer to a similar question, I have worked around this by putting the third extension method into its own class within a subdirectory, thus creating a longer namespace and so more "distance" between it and the calling code than for the version specific to ITupleMatchable<T1, T2>. This feels like a hack to me though. Is there a neater way of resolving this?

Community
  • 1
  • 1
David Arno
  • 42,717
  • 16
  • 86
  • 131
  • 1
    `ITupleMatchable obj = new SomeClass(1, "a");` should work, shouldn't it? – Alex Sikilinda Jul 10 '15 at 16:32
  • 1
    If your question is mainly the one in your question title, then I think Eric Lippert's post that you linked answers that pretty conclusively, so this question is really a duplicate of that one. If you're looking for alternative solutions, consider changing the question in your title to reflect that. – sstan Jul 10 '15 at 16:32
  • @sstan, Eric's answer explains how to work around the problem (as explained in my question). It doesn't address my question though of why a less specific signature is being chosen over a more specific one (as far as I can see). I'd welcome you correcting me though, by pointing out what I'm missing :) – David Arno Jul 10 '15 at 16:36
  • @AlexSikilinda, it would, yes. But I don't want to force users of my library to cast like that. – David Arno Jul 10 '15 at 17:15

1 Answers1

3

If you simply cast new SomeClass(1, "a") to ITupleMatchable<int, string>, it will work fine:

var obj = (ITupleMatchable<int, string>)new SomeClass(1, "a");
obj.Match().With(1, "a");

Remember that your obj variable otherwise has a compile-time type of SomeClass. The compiler can "more easily" match the actual class to the third extension method (which is compatible with any type), than it can by having to look at the interface implementations of SomeClass and then matching it to e.g. the second extension method.

But if you provide the this parameter as the actual interface type, then the second extension method is a better fit, because it's exactly the type the method is looking for, rather than being the broader "any type". I.e. it's a more specific declaration, and so is "better".

Note that once the candidate set of extension methods is found (via rules relating to the namespace, etc.), the actual method is determined using normal overload resolution. I.e. having determined that at least one method in your MatcherExtensions class is an eligible extension method, the compiler then goes with the normal overload resolution rules to pick among those. You can find those rules in the C# 5.0 specification, section 7.5.3.

Briefly though: before applying the overload resolution rules (indeed, in order to determine which methods are even eligible), note that the compiler has already decided the generic type parameters. So, as it evaluates the overload resolution, it is looking at Match(SomeClass item) and Match(ITupleMatchable<int, string> item). Hopefully once you consider that, you'll see why, if the variable has the type SomeClass, the compiler picks your third extension preferentially over the second, and vice a versa if the type is ITupleMatchable<int, string>.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
  • So in short, the answer to my question is "yes" :) I don't want to force users to have to cast to the interface type, so I'll stick with Eric's "distance" workaround. Many thanks for such a well explained and detailed answer. – David Arno Jul 10 '15 at 17:14