【问题标题】:params overload apparent ambiguity - still compiles and works?参数重载明显的歧义 - 仍然编译和工作?
【发布时间】:2014-01-07 16:36:41
【问题描述】:

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

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。

【问题讨论】:

  • Eric Lippert 的这篇文章最近讨论了如何解决重载问题:ericlippert.com/2013/12/23/closer-is-better
  • @Chris 很公平,它可能不直接适用。但鉴于 params 可以从调用参数中完全省略(参见此处:msdn.microsoft.com/en-us/library/w5zay9db.aspx),那么我更接近于“定义的第一个”。
  • @CharlieKilian 这篇博文中没有关于声明顺序的内容。
  • 有趣的事实:即使这有效,Resharper 7.1 仍将其视为错误。
  • 似乎它认为通用对象在参数中“更接近”。如果两者都是通用的或两者都是非通用的,那么它会抱怨歧义。我不能告诉你为什么会这样,但我怀疑规范中的某处某处说这是它的方式。

标签: c# overloading params roslyn


【解决方案1】:

这显然是重载决议中的一个错误。

它在 C# 5 和 C# 3 中重现,但在 Roslyn 中不重现;我不记得我们是否决定故意进行重大更改,或者这是一次意外。 (我的机器上现在没有 C# 4,但如果它在 3 和 5 中重现,那么它几乎肯定也会在 4 中重现。)

我已提请我在 Roslyn 团队的前同事注意这一点。如果他们有任何有趣的事情回复我,我会更新这个答案。

由于我不再能够访问 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();
    }
}

这似乎与元素类型的通用性有关。奇怪的是,正如 Chris 在他的回答中指出的那样,编译器选择了更通用的那个!我原以为错误是另一种方式,并选择不太通用的方式。

顺便说一句,这个错误很可能是我的错,因为我在 C# 3 中的重载解析算法上做了相当多的工作。为错误道歉。

更新

我在 Roslyn 团队的间谍告诉我,这是一个长期存在于过载解决方案中的已知错误。实施了一个决胜规则,该规则从未被记录或证明是说具有 更大的泛型 arity 的类型是更好的类型。这是一个没有任何理由的奇怪规则,但它从未从产品中删除。 Roslyn 团队前段时间决定进行重大更改并修复重载解决方案,以便在这种情况下产生错误。 (我不记得那个决定了,但我们对这类事情做了很多很多决定!)

【讨论】:

  • 感谢您的回答。这并没有给我们带来任何麻烦,我是出于好奇而询问的。我很高兴我为改进语言/编译器做出了贡献 =)
  • @HighCore:不客气;感谢克里斯引起我的注意。以后升级时 Roslyn 突然开始报告此错误会不会给您带来麻烦?
  • 不,我(以及我团队的其他成员也同意这一点)更喜欢编译器尽可能多地捕获并消除歧义。我们可以通过添加重载T Find&lt;T&gt;(this ObjectSet&lt;T&gt; set, int id){...} 轻松解决这个问题
  • @EricLippert:感谢您的反馈。我会尽量不养成向你求助的习惯。:)
【解决方案2】:

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 中的规则将每个函数成员与所有其他函数成员进行比较,则最好的函数成员是在给定参数列表方面优于所有其他函数成员的一个函数成员。

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

【讨论】:

  • 令人困惑的是,它们的扩展形式应该是完全相同的(它添加了 0 个参数,因此它们的扩展形式与您已经注意到的相同)所以我不明白它如何区分之后那...
  • @Chris 查看我提供的 IDEONE 链接。这似乎在 MONO 上工作正常,所以这似乎是一个错误。
  • 这绝对看起来像一个错误。唯一的问题是在哪一个。我个人不相信自己能完美地阅读规范及其所有复杂的语言。 ;-) 或者它可能已经发生了变化,我们正在比较不同的规范版本......而且迟来的 +1,因为我已经意识到我还没有这样做。
【解决方案3】:

这不是一个完整的答案,因为它解释了差异,但没有解释原因。为了完整性,它确实需要规范参考。但是,我不希望我所做的研究迷失在 cmets 中,因此我将其发布为答案。

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

也就是说,如果 Expression&lt;...&gt; 类型更改为 int,编译器会抱怨歧义。如果类型都是通用的,则类似,那么它会抱怨歧义。

下面的 sn-p 会更简单地表现出这种行为:

void Main()
{
    TestMethod();
}

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

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

这将打印“通用”。

【讨论】:

  • 那将与C# specification相反:If MP is a non-generic method and MQ is a generic method, then MP is better than MQ.
  • 我的电脑上没有办公室,所以我无法查看规范的副本,但 sn-p 已经过测试,而不是猜测输出。但是,您的报价指的是泛型方法,而不是方法上的泛型参数。此外,它必须是 params 类型参数的事实将在某处相关......
  • 不幸的是,将另一个重载的参数类型更改为Action&lt;T&gt; 并没有改变任何东西。
  • 不幸的是,这实际上并没有回答问题。
  • @Yandros:那是指方法的通用性,而不是形参类型。也就是说,如果我们在 M(int)M&lt;T&gt;(T) 两个方法之间发生冲突,其中 Tint,那么非泛型方法获胜。
猜你喜欢
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多