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

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

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

Ответ 1

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

Bar x = 0; // Implicit conversion

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

Скрывает ли это все?


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

const decimal DecimalZero = 0.0m;

...
Bar x = DecimalZero;

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

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

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

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

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

Ответ 2

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

Хорошо, я нашел этот, который цитирует следующее и присваивает его Eric Gunnerson:

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

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

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

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

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

который был также уродлив.

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

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

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

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