接口的扩展方法是否被视为优先级低于特定的扩展方法?

时间:2015-07-10 16:27:04

标签: c#

我有以下扩展类:

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); }
}

如果我创建一个元组并调用Match(),它会正确使用第一个扩展方法:

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

如果我创建一个int并调用Match(),它会正确使用最后一个扩展方法:

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

但是,如果我创建SomeClass,它实现ITupleMatchable<int, string>并尝试匹配,编译器仍会选择第三种扩展方法,而不是第二种扩展方法,尽管后者是更具体的匹配:

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

基于Eric Lippert's answer to a similar question,我通过将第三个扩展方法放在子目录中的自己的类中来解决这个问题,从而创建一个更长的命名空间,因此它与调用代码之间的距离比特定于ITupleMatchable<T1, T2>的版本。这对我来说就像是一个黑客。有没有更简洁的解决方法?

1 个答案:

答案 0 :(得分:3)

如果您只是将new SomeClass(1, "a")投射到ITupleMatchable<int, string>,它就可以正常工作:

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

请记住,您的obj变量的编译时类型为SomeClass。编译器可以更容易地&#34;将实际类与第三个扩展方法(与任何类型兼容)匹配,而不是必须查看SomeClass的接口实现,然后将其与例如第二种扩展方法。

但是如果你提供this参数作为实际的接口类型,那么第二种扩展方法更适合,因为它正是该方法所寻找的类型,而不是更广泛的&#34;任何类型&#34;。即它是一个更具体的声明,因此更好&#34;。

请注意,一旦找到候选扩展方法集(通过与命名空间相关的规则等),就会使用正常的重载决策来确定实际方法。即确定您的MatcherExtensions类中至少有一个方法是符合条件的扩展方法后,编译器会按照正常的重载决策规则进行选择。您可以在C#5.0规范的7.5.3部分找到这些规则。

简要说明:在应用重载决策规则之前(实际上,为了确定哪些方法符合条件),请注意编译器已经确定了泛型类型参数。因此,在评估重载决策时,它会查看Match(SomeClass item)Match(ITupleMatchable<int, string> item)。希望一旦你考虑到这一点,你就会明白为什么,如果变量的类型为SomeClass,编译器会优先选择第三个扩展名,而反之亦然,如果类型为ITupleMatchable<int, string>则反之亦然。