18

首先,一点背景。阅读此处发布的问题和接受的答案,了解我的问题的具体情况。我不确定是否存在其他类似的案例,但这是我知道的唯一案例。

上面的“怪癖”是我很早就意识到的。直到最近,我才完全了解原因。

微软关于SqlParameter该类的文档更加清楚地说明了这种情况。

当您Object在 value 参数中指定 an 时,SqlDbType会从 Object 的 Microsoft .NET Framework 类型推断出。

使用SqlParameter构造函数的此重载来指定整数参数值时要小心。由于此重载采用 type 的值Object,因此必须在值为 0 时Object将整数值转换为 类型,如以下 C# 示例所示。

Parameter = new SqlParameter("@pname", Convert.ToInt32(0));

如果您不执行此转换,编译器会假定您正在尝试调用 SqlParameter (string, SqlDbType) 构造函数重载。

(强调补充)

我的问题是为什么编译器会假定当您指定硬编码的“0”(并且只有值“0”)时,您正在尝试指定枚举类型,而不是整数类型?在这种情况下,它假定您正在声明SqlDbType值,而不是值 0。

这是不直观的,更糟糕​​的是,错误是不一致的。我有我编写的旧应用程序多年来一直调用存储过程。我将对应用程序进行更改(通常甚至与我的 SQL Server 类无关),发布更新,这个问题会突然破坏应用程序。

当一个包含多个方法签名的对象包含两个相似的签名,其中一个参数是对象/整数而另一个接受枚举时,为什么编译器会被值 0 混淆?

正如我所提到的,我从未将这视为任何其他类上的任何其他构造函数或方法的问题。SqlParameter这是该类独有的还是 C#/.Net 中继承的错误?

4

5 回答 5

19

这是因为零整数可以隐式转换为枚举:

enum SqlDbType
{
    Zero = 0,
    One = 1
}

class TestClass
{
    public TestClass(string s, object o)
    { System.Console.WriteLine("{0} => TestClass(object)", s); } 

    public TestClass(string s, SqlDbType e)
    { System.Console.WriteLine("{0} => TestClass(Enum SqlDbType)", s); }
}

// This is perfectly valid:
SqlDbType valid = 0;
// Whilst this is not:
SqlDbType ohNoYouDont = 1;

var a1 = new TestClass("0", 0);
// 0 => TestClass(Enum SqlDbType)
var a2 = new TestClass("1", 1); 
// => 1 => TestClass(object)

(改编自Visual C# 2008 Breaking Changes - change 12

当编译器执行重载决议时,0 是适用于 the和构造函数的函数成员,因为:SqlDbTypeobject

存在从实参类型到相应形参类型的隐式转换(第 6.1 节)

(两者SqlDbType x = 0object x = 0有效)

由于更好的转换规则SqlDbType,参数优于object参数:

  • 如果T1T2是同一类型,则两种转换都不是更好。
    • object并且SqlDbType不是同一类型
  • 如果ST1C1是更好的转换。
    • 0不是一个object
  • 如果ST2C2是更好的转换。
    • 0不是一个SqlDbType
  • 如果存在从 to 的隐式转换,并且不存在从T1toT2的隐式转换T2T1则为C1更好的转换。
    • 不存在隐式object转换SqlDbType
  • 如果存在从 to 的隐式转换,并且不存在从T2toT1的隐式转换T1T2则为C2更好的转换。
    • SqlDbType存在从to的隐式转换object,所以SqlDbType是更好的转换

请注意,正如@Eric 在他的回答中所解释的那样,在Visual C# 2008 (微软对 C# 规范的实现)中,常量 0 的确切构成已经(相当微妙地)发生了变化。

于 2013-01-08T22:08:46.560 回答
16

RichardTowers 的回答非常好,但我想我会补充一点。

正如其他答案所指出的那样,行为的原因是(1)零可以转换为任何枚举,并且显然可以转换为对象,以及(2)任何枚举类型都比对象更具体,因此采用枚举的方法是因此通过重载决议选择作为更好的方法。第二点是我希望不言自明的,但是什么解释了第一点呢?

首先,不幸的是,这里与规范存在偏差。规范说任何文字zero,即0实际出现在源代码中的数字,可以隐式转换为任何枚举类型。编译器实际上实现了可以因此转换任何常量零。原因是因为一个错误,编译器有时会允许常量零,有时不允许,以一种奇怪和不一致的方式。解决问题的最简单方法是始终允许恒定零。您可以在此处详细了解此内容:

https://web.archive.org/web/20110308161103/http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one。 aspx

其次,允许将零转换为任何枚举的原因是为了确保始终可以将“标志”枚举归零。良好的编程习惯是每个“标志”枚举都有一个等于零的值“无”,但这是一个指导方针,而不是要求。C# 1.0 的设计者认为您可能不得不说这看起来很奇怪

for (MyFlags f = (MyFlags)0; ...

初始化一个本地。我个人的看法是,这个决定造成的麻烦比它的价值要多,无论是对上述错误的悲痛,还是它在您发现的过载解决方案中引入的奇怪问题。

最后,构造函数的设计者可能一开始就意识到这将是一个问题,并制作了重载的签名,以便开发人员可以清楚地决定调用哪个 ctor 而无需插入强制转换。不幸的是,这是一个非常模糊的问题,所以很多设计师都没有意识到这一点。希望任何阅读本文的人都不会犯同样的错误;如果您希望这两个覆盖具有不同的语义,请不要在 object 和任何枚举之间产生歧义

于 2013-01-09T15:09:27.603 回答
1

这显然是一种已知行为,会影响同时存在枚举和对象类型的任何函数重载。我不完全理解,但 Eric Lippert 在他的博客上总结得很好

于 2013-01-08T21:49:48.207 回答
1

这是由于整数文字 0 具有到任何枚举类型的隐式转换。C# 规范指出:

6.1.3 隐式枚举转换

隐式枚举转换允许将十进制整数文字 0 转换为任何枚举类型和任何其基础类型为枚举类型的可空类型。在后一种情况下,通过转换为基础枚举类型并包装结果来评估转换。

因此,在这种情况下,最具体的重载是SqlParameter(string, DbType).

这不适用于其他 int 值,因此SqlParameter(string, object)构造函数是最具体的。

于 2013-01-08T21:56:40.177 回答
0

在解析重载方法的类型时,C# 会选择最具体的选项。SqlParameter类有两个构造函数,它们恰好采用两个参数,SqlParameter(String, SqlDbType)并且SqlParameter(String, Object). 当您提供字面0量时,它可以被解释为 Object 或 SqlDbType。由于 SqlDbType 比 Object 更具体,因此假定它是意图。

您可以在此答案中阅读有关重载解决方案的更多信息。

于 2013-01-08T21:50:02.110 回答