接口的扩展方法是否优先级低于较不具体的方法?

11

我有以下扩展类:

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.

然而,如果我创建一个实现了ITupleMatchable<int, string>接口的SomeClass类,并尝试进行匹配,编译器仍会选择第三个扩展方法,而不是第二个,尽管后者更具体:
var obj = new SomeClass(1, "a");
obj.Match().With(1, "a")... // won't compile.

根据Eric Lippert类似问题的答案,我通过将第三个扩展方法放入子目录中的自己的类中来解决了这个问题,因此创建了一个更长的命名空间,使其与调用代码相比具有更多的“距离”,而不是特定于ITupleMatchable<T1,T2>的版本。然而,这对我来说感觉像是一个hack。有没有更简洁的方法来解决这个问题?


1
ITupleMatchable obj = new SomeClass(1, "a"); 应该可以工作,不是吗? - Alex Sikilinda
1
如果你的问题主要是标题中的那个问题,那么我认为你链接的Eric Lippert的帖子已经非常明确地回答了这个问题,所以这个问题实际上是重复的。如果你正在寻找替代方案,请考虑更改你标题中的问题以反映出这一点。 - sstan
@sstan,Eric的回答解释了如何解决问题(正如我在问题中所解释的)。然而,它并没有回答我的问题,即为什么一个不太具体的签名被选择而不是一个更具体的签名(就我所看到的而言)。如果我错了,欢迎您指出我漏掉了什么 :) - David Arno
@AlexSikilinda,是的,它会。但我不想强制我的库的用户这样转换类型。 - David Arno
1个回答

3

如果您只是将new SomeClass(1, "a")转换为ITupleMatchable<int, string>,它将正常工作:

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

请记住,您的obj变量在编译时的类型是SomeClass。编译器可以“更容易”地将实际类与第三个扩展方法相匹配(与任何类型兼容),而不必查看SomeClass的接口实现,然后将其与例如第二个扩展方法进行匹配。
但是,如果您将this参数提供为实际接口类型,则第二个扩展方法更适合,因为它恰好是该方法正在寻找的类型,而不是更广泛的“任何类型”。也就是说,它是一个更具体的声明,因此是“更好的”。
请注意,一旦找到扩展方法的候选集(通过与命名空间等相关的规则),则使用正常重载解析确定实际方法。也就是说,在确定了您的MatcherExtensions类中至少有一个可用的扩展方法之后,编译器会按照正常的重载解析规则从中选择。您可以在C# 5.0规范的第7.5.3节中找到这些规则。
简单地说:在应用重载解析规则之前(确实,在确定哪些方法甚至有资格之前),请注意编译器已经决定了泛型类型参数。因此,在评估重载解析时,它正在查看Match(SomeClass item)Match(ITupleMatchable<int, string> item)。希望一旦您考虑到这一点,您就会看到为什么,如果变量的类型为SomeClass,编译器优先选择第三个扩展,反之亦然,如果类型为ITupleMatchable<int, string>

简而言之,我的问题的答案是“是” :) 我不想强制用户转换为接口类型,所以我将坚持使用Eric的“distance”解决方法。非常感谢你提供如此详细和清晰的答案。 - David Arno

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接