Странное (возможно, неправильное?) поведение компилятора C# с перегрузкой методов и перечислениями

сегодня я обнаружил очень странное поведение с перегрузкой функций С#. Проблема возникает, когда у меня есть метод с двумя перегрузками: один принимает Object, а другой принимает Enum любого типа. Когда я передаю 0 в качестве параметра, вызывается версия метода Enum. Когда я использую любое другое целочисленное значение, вызывается версия объекта. Я знаю, что это можно легко исправить с помощью явного приведения типов, но я хочу знать, почему компилятор ведет себя таким образом. Это ошибка или просто какое-то странное языковое правило, о котором я не знаю?

Код ниже объясняет проблему (проверено на среде выполнения 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");
    }
}

person Grzegorz Kyc    schedule 30.06.2010    source источник
comment
Примечание: если есть перегрузка, которая принимает параметр int, она будет иметь приоритет над версией enum при передаче 0.   -  person jball    schedule 01.07.2010


Ответы (2)


Возможно, вы не знаете, что существует неявное преобразование из константы1, равной 0, в любое перечисление:

Bar x = 0; // Implicit conversion

Теперь преобразование из 0 в Bar является более конкретным, чем преобразование из 0 в object, поэтому используется перегрузка Foo(Bar).

Это все проясняет?


1 На самом деле в компиляторе Microsoft C# есть ошибка, которая позволяет использовать любую нулевую константу, а не только целое число:

const decimal DecimalZero = 0.0m;

...
Bar x = DecimalZero;

Маловероятно, что это когда-либо будет исправлено, так как это может привести к поломке существующего рабочего кода. Я полагаю, что у Эрика Липперта есть два блог сообщения, в которых содержится гораздо больше деталей.

В разделе 6.1.3 спецификации С# (спецификация С# 4) об этом сказано следующее:

Неявное преобразование перечисления позволяет преобразовать десятичный-целочисленный-литерал 0 в любой тип перечисления и в любой тип, допускающий значение NULL, чей базовый тип является перечислением- тип. В последнем случае преобразование оценивается путем преобразования в базовый enum-type и переноса результата (§4.1.10).

На самом деле это говорит о том, что ошибка заключается не только в разрешении неправильного типа, но и в разрешении преобразования любого постоянного значения 0, а не только буквального значения 0.

EDIT: похоже, что постоянная часть была частично появился в компиляторе C# 3. Раньше это были некоторые постоянные значения, теперь похоже, что это все.

person Jon Skeet    schedule 30.06.2010

Я знаю, что где-то читал, что система .NET всегда рассматривает ноль как допустимое значение перечисления, даже если на самом деле это не так. Я постараюсь найти ссылку на это...

Хорошо, я нашел это, в котором цитируется следующее и атрибутируется Эрику Ганнерсону:

Перечисления в С# имеют двойное назначение. Они используются для обычного использования перечисления, а также для битовых полей. Когда я имею дело с битовыми полями, вы часто хотите объединить значение с битовым полем и проверить, верно ли оно.

Наши первоначальные правила означали, что вы должны были написать:

если ((myVar & MyEnumName.ColorRed) != (MyEnumName) 0)

который мы думали, что было трудно читать. Одним из вариантов было определить нулевую запись:

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

что тоже было некрасиво.

Поэтому мы решили немного смягчить наши правила и разрешить неявное преобразование буквального нуля в любой тип перечисления, что позволяет вам написать:

если ((myVar & MyEnumName.ColorRed) != 0)

вот почему PlayingCard(0, 0) работает.

Таким образом, похоже, что вся причина этого заключалась в том, чтобы просто разрешить приравнивание к нулю при проверке флагов без необходимости приведения нуля.

person jdmichal    schedule 30.06.2010