我有以下扩展类:
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); }
}
Run Code Online (Sandbox Code Playgroud)
如果我创建一个元组并调用Match()它,它正确使用第一个扩展方法:
var tuple = Tuple.Create(1, "a");
tuple.Match().With(1, "a")... // compiles just fine.
Run Code Online (Sandbox Code Playgroud)
如果我创建一个int并调用Match()它,它正确使用最后一个扩展方法:
var one = 1;
one.Match().With(1)... // compiles just fine.
Run Code Online (Sandbox Code Playgroud)
但是,如果我创建SomeClass,实现ITupleMatchable<int, string>并尝试匹配它,编译器仍然选择第三个扩展方法,而不是第二个扩展方法,尽管后者是更具体的匹配:
var obj = new SomeClass(1, "a");
obj.Match().With(1, "a")... // won't compile.
Run Code Online (Sandbox Code Playgroud)
基于Eric Lippert对类似问题的回答,我通过将第三个扩展方法放在子目录中的自己的类中来解决这个问题,从而创建一个更长的命名空间,因此它与调用代码之间的距离比版本更长具体到ITupleMatchable<T1, T2>.这对我来说就像是一个黑客.有没有更简洁的解决方法?
如果你简单地转换new SomeClass(1, "a")为ITupleMatchable<int, string>,它会正常工作:
var obj = (ITupleMatchable<int, string>)new SomeClass(1, "a");
obj.Match().With(1, "a");
Run Code Online (Sandbox Code Playgroud)
请记住,您的obj变量的编译时类型为SomeClass. 编译器可以“更轻松地”将实际类与第三个扩展方法(与任何类型兼容)匹配,而不是通过查看其接口实现SomeClass然后将其与第二个扩展方法进行匹配。
但是,如果您提供this参数作为实际的接口类型,那么第二个扩展方法更合适,因为它正是该方法正在寻找的类型,而不是更广泛的“任何类型”。即,这是一个更具体的声明,因此是“更好”。
请注意,一旦找到扩展方法的候选集(通过与命名空间相关的规则等),就使用正常的重载解析来确定实际方法。即,在确定类中至少有一个方法MatcherExtensions是合格的扩展方法后,编译器就会按照正常的重载解析规则在这些方法中进行选择。您可以在 C# 5.0 规范的 部分找到这些规则7.5.3。
简而言之:在应用重载解析规则之前(实际上,为了确定哪些方法是合格的),请注意编译器已经决定了泛型类型参数。因此,当它评估重载决策时,它会查看Match(SomeClass item)和Match(ITupleMatchable<int, string> item)。希望一旦您考虑到这一点,您就会明白为什么,如果变量的类型为SomeClass,编译器会优先选择第三个扩展名而不是第二个扩展名,如果类型为 ,反之亦然ITupleMatchable<int, string>。