Странное (возможно, неправильное?) Поведение компилятора С# с перегрузкой и перечислением метода
сегодня я обнаружил очень странное поведение при перегрузке функции С#. Проблема возникает, когда у меня есть метод с 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).
Таким образом, кажется, что вся причина этого заключалась в том, чтобы просто разрешить приравнивание нулю при проверке флагов без необходимости использовать нуль.