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

问题描述 投票:0回答:2

今天我发现了 C# 函数重载的一个非常奇怪的行为。当我有一个具有 2 个重载的方法时,就会出现问题,一个重载接受 Object,另一个接受任何类型的 Enum。当我传递 0 作为参数时,将调用该方法的 Enum 版本。当我使用任何其他整数值时,将调用对象版本。我知道可以通过使用显式转换轻松解决这个问题,但我想知道为什么编译器会这样做。这是一个错误还是只是一些我不知道的奇怪的语言规则?

下面的代码解释了问题(使用运行时2.0.50727检查)

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");
    }
}
c# enums compiler-errors
2个回答
13
投票

您可能没有意识到存在从常量1 0 到任何枚举的隐式转换:

Bar x = 0; // Implicit conversion

现在,从 0 到

Bar
的转换比从 0 到
object
的转换更具体,这就是使用
Foo(Bar)
重载的原因。

这样就一切都清楚了吗?


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

const decimal DecimalZero = 0.0m;

...
Bar x = DecimalZero;

这个问题不太可能被修复,因为它可能会破坏现有的工作代码。我相信 Eric Lippert 有两篇blogposts,其中有更多细节。

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

隐式枚举转换 允许 小数-整数-文字 0 转换为任何枚举类型并且 任何 nullable-type 其底层 类型是枚举类型。在后者中 如果转换是通过以下方式评估的 转换为底层 enum-type 并包装结果(§4.1.10)。

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

编辑:看起来“常量”部分是在 C# 3 编译器中部分引入的。以前是一些常量值,现在看起来是全部。


3
投票

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

好吧,我找到了this,它引用了以下内容并将其归因于 Eric Gunnerson:

C# 中的枚举有双重用途。它们用于通常的枚举用途,也用于位字段。当我处理位字段时,您经常需要将一个值与位字段进行 AND 运算并检查它是否为真。

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

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

我们认为这很难读。一种替代方法是定义零条目:

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

这也很丑。

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

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

这就是 PlayingCard(0, 0) 起作用的原因。

因此,这背后的全部原因似乎只是在检查标志时允许等于零,而不必强制转换零。

© www.soinside.com 2019 - 2024. All rights reserved.