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?
The only advantage of extension methods is code readability.
And static methods and properties and methods are not thread safe and therefore should be avoided then extension methods and extension properties are bad. We are just tricked to do those because the codes we write will appear as pretty or clean but performance-wise it is not.
Extension methods cannot be overridden the way classes and instance methods are. They are overridden by a slight trick in how the compiler selects which extension method to use by using "closeness" of the method to the caller via namespaces.
An extension method must be defined in a top-level static class. An extension method with the same name and signature as an instance method will not be called. Extension methods cannot be used to override existing methods. The concept of extension methods cannot be applied to fields, properties or events.
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>
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With