问题 奇怪的(可能是错的?)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");
    }
}

7782
2018-06-30 22:20


起源

注意:如果存在带有int参数的重载,则传递时它将优先于枚举版本 0。 - jball


答案:


可能是您不知道存在来自常量的隐式转换1 0到任何枚举:

Bar x = 0; // Implicit conversion

现在,从0转换为 Bar 比从0到0的转换更具体 object,这就是为什么 Foo(Bar) 使用过载。

这清楚了吗?


1 实际上,Microsoft C#编译器中存在一个错误 任何 零常数,而不仅仅是一个整数:

const decimal DecimalZero = 0.0m;

...
Bar x = DecimalZero;

它不太可能被修复,因为它可能会破坏现有的工作代码。我相信Eric Lippert有两个 博客  帖子 更详细的内容。

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

隐式枚举转换   允许的 小数整数字面 0   转换为任何枚举类型和   去任何 可空类型 其底层   类型是 枚举类型。在后者   案例转换由评估   转换为底层 枚举类型   并包装结果(§4.1.10)。

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

编辑:看起来像“常量”部分 部分介绍在C#3编译器中。以前是 一些 常数值,现在看来它们都是它们。


13
2018-06-30 22:22



你要找的帖子是 blogs.msdn.com/b/ericlippert/archive/2006/03/28/... 和 blogs.msdn.com/b/ericlippert/archive/2006/03/29/...。在允许编译器使用任何常量零时,我意外地使它使用任何类型的任何常量零,而不仅仅是整数类型。我为导致这场混乱的所有错误感到遗憾。 - Eric Lippert


我知道我已经在其他地方读过.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)有效。

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


3
2018-06-30 22:23