今天我发现了一个非常奇怪的C#函数重载行为。当我有一个方法有2个重载,一个接受对象,另一个接受任何类型的枚举时,会出现问题。当我将0作为参数传递时,将调用该方法的Enum版本。当我使用任何其他整数值时,将调用Object版本。我知道这可以通过使用显式转换来轻松修复,但我想知道为什么编译器会以这种方式运行。这是一个错误还是我不知道的一些奇怪的语言规则?
下面的代码解释了问题(使用运行时2.0.50727检查)
感谢您对此提供任何帮助, Grzegorz Kyc
class Program
{
enum Bar
{
Value1,
Value2,
Value3
}
static void Main(string[] args)
{
Foo(0);
Foo(1);
Console.ReadLine();
}
static void Foo(object a)
{
Console.WriteLine("object");
}
static void Foo(Bar a)
{
Console.WriteLine("enum");
}
}
答案 0 :(得分:13)
可能您不知道从0的常量 1 到任何枚举的隐式转换:
Bar x = 0; // Implicit conversion
现在,从0到Bar
的转换比从0到object
的转换更具体,这就是使用Foo(Bar)
重载的原因。
这清楚了吗?
1 Microsoft C#编译器实际上存在一个错误,它允许任何零常量,而不仅仅是一个整数:
const decimal DecimalZero = 0.0m;
...
Bar x = DecimalZero;
这不太可能被解决,因为它可能会破坏现有的工作代码。我相信Eric Lippert有两个blog posts,它会更详细。
C#规范第6.1.3节(C#4规范)对此有这样的说法:
隐式枚举转换 允许 decimal-integer-literal 0 转换为任何枚举类型和 任何 nullable-type 的底层证据 type是枚举类型。在后者 案例转换由评估 转换为基础枚举类型 并包装结果(§4.1.10)。
这实际上表明错误不仅仅是允许错误的类型,而是允许转换任何常量0值而不是仅转换为字面值0。
编辑:看起来“常量”部分是partially introduced in the C# 3 compiler。以前它是一些常量值,现在它看起来就像它们一样。
答案 1 :(得分:3)
我知道我已经在其他地方读过.NET系统总是将零视为有效的枚举值,即使实际上并非如此。我会尝试为此找到一些参考......
好的,我找到了this,引用了以下内容并将其归于Eric Gunnerson:
C#中的枚举有双重用途。它们用于通常的枚举使用,它们也用于位字段。当我处理位字段时,你经常想要用位字段对一个值进行AND运算并检查它是否为真。
我们的初始规则意味着你必须写:
if((myVar& MyEnumName.ColorRed)!=(MyEnumName)0)
我们认为很难读懂。一个alernative是定义零条目:
if((myVar& MyEnumName.ColorRed)!= MyEnumName.NoBitsSet)
这也很难看。
因此我们决定稍微放松一下规则,并允许从字面零到任何枚举类型的隐式转换,这允许你写:
if((myVar& MyEnumName.ColorRed)!= 0)
这就是PlayCard(0,0)的原因。
所以看起来这背后的全部原因是在检查标志时简单地允许等于零而不必抛出零。