奇怪的(可能是错的?)C#编译器行为与方法重载和枚举

今天我发现了一个非常奇怪的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的常量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有两篇博文更详细。

C#规范第6.1.3节(C#4规范)对此有这样的说法:

隐式枚举转换允许将decimal-integer-literal 0转换为任何枚举类型和任何可以为其类型为枚举 类型可空 类型 。 在后一种情况下,通过转换为基础枚举类型并包装结果来评估转换(第4.1.10节)。

这实际上表明该错误不仅仅是允许错误的类型,而是允许转换任何常量0值而不仅仅是字面值0。

编辑:看起来“常量”部分在C#3编译器中部分引入 。 以前它是一些不变的值,现在它看起来就像它们一样。

我知道我已经在其他地方读过.NET系统总是将零视为有效的枚举值,即使实际上并非如此。 我会尝试为此找到一些参考……

好吧,我发现了这个 ,它引用了以下内容并将其归于Eric Gunnerson:

C#中的枚举做双重目的。 它们用于通常的枚举使用,它们也用于位字段。 当我处理位字段时,您经常希望使用位字段对一个值进行AND运算并检查它是否为真。

我们的初始规则意味着你必须写:

if((myVar&MyEnumName.ColorRed)!=(MyEnumName)0)

我们认为很难读懂。 一个alernative是定义零条目:

if((myVar&MyEnumName.ColorRed)!= MyEnumName.NoBitsSet)

这也很难看。

因此,我们决定放宽我们的规则,并允许从字面零到任何枚举类型的隐式转换,这允许您编写:

if((myVar&MyEnumName.ColorRed)!= 0)

这就是为什么PlayingCard(0,0)有效。

所以看来这背后的全部原因是在检查标志时简单地允许等于零而不必抛出零。