params超载明显模糊 - 仍然编译和工作?

时间:2014-01-07 16:36:41

标签: c# overloading params roslyn

我们在代码中找到了这些:

public static class ObjectContextExtensions
{

    public static T Find<T>(this ObjectSet<T> set, int id, params Expression<Func<T, object>>[] includes) where T : class
    {
        ...
    }

    public static T Find<T>(this ObjectSet<T> set, int id, params string[] includes) where T : class
    {
       ...
    }
}

如您所见,除了params之外,它们具有相同的签名。

他们正在以多种方式使用,其中之一:

DBContext.Users.Find(userid.Value); //userid being an int? (Nullable<int>)

,对我来说奇怪的是,它解决了第一次超载。

Q1:为什么不会产生编译错误?

Q2:为什么C#编译器将上述调用解析为第一种方法?

编辑:为了澄清,这是C#4.0,.Net 4.0,Visual Studio 2010。

3 个答案:

答案 0 :(得分:29)

这显然是重载解析中的一个错误。

它在C#5和C#3中再现,但在Roslyn中没有;我不记得我们是否决定故意改变或者这是一次意外。 (我现在的机器上没有C#4,但如果它在3和5中重新编写,那么它几乎可以肯定在4中。)

我已将它引起罗斯林团队前同事的注意。如果他们回复我的任何有趣的东西,我会更新这个答案。

由于我无法访问C#3/4/5源代码,因此我无法说明错误的原因。考虑在connect.microsoft.com上报告。

这是一个非常简化的复制品:

class P
{
    static void M(params System.Collections.Generic.List<string>[] p) {}
    static void M(params int[] p)  {}
    static void Main()
    {
        M();
    }
}

它似乎与元素类型的通用性有关。奇怪的是,正如克里斯在他的回答中指出的那样,编译器会选择更通用的一个!我本来希望这个bug是另一种方式,并选择不那么通用的。

顺便提一下,这个错误可能是我的错,因为我在C#3中对重载解析算法做了大量的工作。为错误道歉。

更新

我在罗斯林队的间谍告诉我,这是一个在超载决议中长期存在的已知错误。实施了一个决胜局规则,从未记录或证明有更大的通用arity 的类型是更好的类型。这是一个奇怪的规则,没有任何理由,但它从未被从产品中删除。 Roslyn团队前段时间决定采取重大改变并修复重载决策,以便在这种情况下产生错误。 (我不记得那个决定,但是我们做了很多关于这类事情的决定

答案 1 :(得分:7)

On IDEONE the compiler successfully produces an error。如果您逐步分析分辨率算法,那么它应该是一个错误:

  

1)构造方法调用的候选方法集。从与M关联的方法集合开始,这些方法由前一个成员查找[...]找到。集合缩减包括将以下规则应用于集合中的每个方法TN,其中T是方法N的类型声明:

为简单起见,我们可以在此推断出这里的方法集包含了两种方法。

然后减少进行:

  

2)如果N不适用于A(Section 7.4.2.1),则从集合中删除N.

这两种方法适用于扩展形式的Applicable function member规则:

  

通过使用参数数组的元素类型的零个或多个值参数替换函数成员声明中的参数数组来构造展开形式,使得参数列表A中的参数数量与参数的总数相匹配。如果A的参数少于函数成员声明中固定参数的数量,则无法构造函数成员的展开形式,因此不适用。

此规则在缩减集中保留两种方法。

实验(在一种或两种方法中将id参数类型更改为float)确认两个函数都保留在候选集中,并进一步区分implicit conversion comparison rules

这表明上述算法在创建候选集方面工作正常,并且不依赖于某些内部方法排序。由于唯一可以进一步区分方法的是Overload resolution rules ,这似乎是一个错误,因为:

  

最佳函数成员是一个函数成员,它比给定参数列表中的所有其他函数成员更好,前提是使用Section 7.4.2.2中的规则将每个函数成员与所有其他函数成员进行比较。

并且显然这些方法都没有比其他方法更好,因为这里不存在隐式转换。

答案 2 :(得分:6)

这不是一个完整的答案,因为它解释了差异而不是原因。它确实需要完整性的规范参考。但是我不想让我所做的研究在评论中迷失,所以我发帖作为答案。

两个重载之间的区别在于,一个的参数是通用的而另一个不是。编译器似乎认为泛型类型比非泛型更接近。

即如果Expression<...>类型更改为int,编译器会抱怨模糊性。类似如果类型都是通用的,那么就会抱怨模棱两可。

以下代码段将更简单地展示此行为:

void Main()
{
    TestMethod();
}

public void TestMethod(params string[] args)
{
    Console.WriteLine("NonGeneric");
}

public void TestMethod(params List<string>[] args)
{
    Console.WriteLine("Generic");
}

这将打印“Generic”。