C# 编译器值类型解析和硬编码;0“;整数值

C# 编译器值类型解析和硬编码;0“;整数值,c#,.net,compiler-construction,C#,.net,Compiler Construction,首先,一点背景知识。阅读问题并接受我的问题的特定场景的答案。我不确定是否存在其他类似的案例,但这是我知道的唯一案例 上述“怪癖”是我早就意识到的。直到最近,我才完全了解这项事业 微软关于SqlParameter类的文档更清楚地说明了这种情况 当您在value参数中指定一个对象时,SqlDbType是 根据对象的Microsoft.NET Framework类型推断 使用SqlParameter构造函数的此重载时请小心 指定整型参数值。因为这个过载需要一段时间 类型为对象的值,必须将整数值转换为对

首先,一点背景知识。阅读问题并接受我的问题的特定场景的答案。我不确定是否存在其他类似的案例,但这是我知道的唯一案例

上述“怪癖”是我早就意识到的。直到最近,我才完全了解这项事业

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

当您在value参数中指定一个
对象时,
SqlDbType
是 根据对象的Microsoft.NET Framework类型推断

使用
SqlParameter
构造函数的此重载时请小心 指定整型参数值。因为这个过载需要一段时间 类型为
对象的值
,必须将整数值转换为
对象
当值为零时键入,如下C#示例所示

Parameter=newsqlparameter(@pname),Convert.ToInt32(0))

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

(皇帝补充)

我的问题是,当您指定硬编码的“0”(且仅值“0”)时,编译器为什么会假定您正在尝试指定枚举类型,而不是整数类型?在本例中,它假定您正在声明
SqlDbType
值,而不是值0

这是不直观的,更糟糕的是,错误是不一致的。我有一些我编写的老应用程序,这些应用程序调用存储过程已经很多年了。我将对应用程序进行更改(通常情况下甚至与我的SQL Server类没有关联),发布一个更新,这个问题将突然中断应用程序

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


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

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

上很好地总结了这一点。在解析重载方法的类型时,C#选择了最具体的选项。该类有两个构造函数,它们只接受两个参数,
SqlParameter(String,SqlDbType)
SqlParameter(String,Object)
。当您提供文本
0
时,可以将其解释为对象或SqlDbType。由于SqlDbType比Object更具体,因此假定它是目的


您可以在中阅读有关重载解析的更多信息。

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

6.1.3隐式枚举转换

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

因此,本例中最具体的重载是
SqlParameter(string,DbType)


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

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

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)
(改编自)

当编译器执行重载时,对于
SqlDbType
对象
构造函数,解析0都是一个问题,因为:

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

SqlDbType x=0
object x=0
都是有效的)

SqlDbType
参数优于
object
参数,因为:

  • 如果
    T1
    T2
    是同一类型,则两种转换都不好。
    • object
      SqlDbType
      不是同一类型
  • 如果
    S
    T1
    C1
    是更好的转换。
    • 0
      不是
      对象
  • 如果
    S
    T2
    C2
    是更好的转换。
    • 0
      不是
      SqlDbType
  • 如果存在从
    T1
    T2
    的隐式转换,并且不存在从
    T2
    T1
    的隐式转换,
    C1
    是更好的转换。
    • 不存在从
      object
      SqlDbType
      的隐式转换
  • 如果存在从
    T2
    T1
    的隐式转换,并且不存在从
    T1
    T2
    的隐式转换,
    C2
    是更好的转换。
    • 存在从
      SqlDbType
      object
      的隐式转换,因此
      SqlDbType
      是更好的转换

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

RichardTowers的回答非常好,但我想我应该再补充一点

正如其他答案所指出的那样,这种行为的原因是:(1)零可以转换为任何枚举,显然可以转换为对象;(2)任何枚举类型都更容易转换为sp
for (MyFlags f = (MyFlags)0; ...