问题 奇怪的C#编译器行为(重载决议)


我发现以下代码的C#编译器行为非常奇怪:

    var p1 = new SqlParameter("@p", Convert.ToInt32(1));
    var p2 = new SqlParameter("@p", 1);
    Assert.AreEqual(p1.Value, p2.Value); // PASS

    var x = 0;
    p1 = new SqlParameter("@p", Convert.ToInt32(x));
    p2 = new SqlParameter("@p", x);
    Assert.AreEqual(p1.Value, p2.Value); // PASS

    p1 = new SqlParameter("@p", Convert.ToInt32(0));
    p2 = new SqlParameter("@p", 0);
    Assert.AreEqual(p1.Value, p2.Value); // FAIL!?

在最后一行断言失败,并显示以下消息:

  Expected: 0
  But was:  null

我明白为什么测试失败了: p2 = new SqlParameter("@p", 0); 被解决为 SqlParameter(string, SqlDbType) 和其他情况一样 SqlParameter(string, object)。但我不明白为什么会这样。对我来说它看起来像一个bug,但我不相信C#编译器会有这样的bug。

有什么理由吗?

附:对于具有enum参数和0值的任何方法重载(SqlDbType是枚举),这似乎是一个问题。


1750
2017-11-22 14:44


起源



答案:


基本上是十进制整数字面值 0 可隐式转换为所有枚举类型(C#4规范§6.1.3),因此编译器会确定 SqlParameter(string, SqlDbType) 是适用的功能成员。然后它必须在两个候选函数成员之间选择更好,并且它选择 SqlParameter(string, SqlDbType) 过度 SqlParameter(string, object)因为 SqlDbType 是比一个更具体的类型 object (§7.5.3.2)。

但我同意在那种情况下它很混乱......


11
2017-11-22 14:51



只是一个想法,将警告级别设置为最大值会发生什么?也许它会在这种情况下警告。 - dowhilefor
是的,我知道了。我的问题是:为什么它仅适用于0? - Victor Haydin
@VictorHaydin,因为规范是这样说的......“隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型和任何可以为其类型为枚举类型的可空类型”。现在,我不知道为什么它是这样设计的,但可能有一个很好的理由......你需要问一下来自C#团队的人。 - Thomas Levesque


答案:


基本上是十进制整数字面值 0 可隐式转换为所有枚举类型(C#4规范§6.1.3),因此编译器会确定 SqlParameter(string, SqlDbType) 是适用的功能成员。然后它必须在两个候选函数成员之间选择更好,并且它选择 SqlParameter(string, SqlDbType) 过度 SqlParameter(string, object)因为 SqlDbType 是比一个更具体的类型 object (§7.5.3.2)。

但我同意在那种情况下它很混乱......


11
2017-11-22 14:51



只是一个想法,将警告级别设置为最大值会发生什么?也许它会在这种情况下警告。 - dowhilefor
是的,我知道了。我的问题是:为什么它仅适用于0? - Victor Haydin
@VictorHaydin,因为规范是这样说的......“隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型和任何可以为其类型为枚举类型的可空类型”。现在,我不知道为什么它是这样设计的,但可能有一个很好的理由......你需要问一下来自C#团队的人。 - Thomas Levesque