Значение типа компилятора Разрешение и жестко заданные значения "0" Целочисленные значения
Сначала немного фона. Прочтите вопрос и принятый ответ размещенный здесь для конкретного сценария для моего вопроса. Я не уверен, существуют ли другие аналогичные случаи, но это единственный случай, о котором я знаю.
Вышеупомянутая "причуда" - это то, о чем я давно знаю. До недавнего времени я не понимал всю полноту дела.
Документация Microsoft в классе SqlParameter
проливает немного больше света на ситуацию.
Когда вы указываете Object
в параметре значения, SqlDbType
выводится из типа Microsoft.NET Framework объекта.
Соблюдайте осторожность при использовании этой перегрузки конструктора SqlParameter
для указания целочисленных значений параметров. Поскольку эта перегрузка занимает значение типа Object
, вы должны преобразовать интегральное значение в Object
введите , когда значение равно нулю, как демонстрирует следующий пример С#.
Parameter = new SqlParameter("@pname", Convert.ToInt32(0));
Если вы это сделаете не выполнять это преобразование, компилятор предполагает, что вы пытаетесь для вызова перегрузки конструктора SqlParameter (string, SqlDbType).
(добавлено)
Мой вопрос в том, почему компилятор предполагает, что когда вы указываете жестко закодированное "0" (и только значение "0" ), которое вы пытаетесь указать тип перечисления, а не целочисленный тип? В этом случае предполагается, что вы объявляете SqlDbType
значение, а не значение 0.
Это неинтуитивно, и, что еще хуже, ошибка является непоследовательной. У меня есть старые приложения, которые я написал, которые называли хранимые процедуры годами. Я внесу изменения в приложение (часто иногда даже не связанные с моими классами SQL Server), публикую обновление, и эта проблема внезапно сломает приложение.
Почему компилятор запутался в значении 0, когда объект, содержащий несколько сигнатур методов, содержит две одинаковые подписи, где один параметр является объектом/целым числом, а другой принимает перечисление?
Как я уже упоминал, я никогда не видел, чтобы это было проблемой с любым другим конструктором или методом в любом другом классе. Является ли это уникальным для класса SqlParameter
или это ошибка наследования внутри С#/. Net?
Ответы
Ответ 1
Это потому, что нулевое целое неявно конвертируется в перечисление:
enum SqlDbType
{
Zero = 0,
One = 1
}
class TestClass
{
public TestClass(string s, object o)
{ System.Console.WriteLine("{0} => TestClass(object)", s); }
public TestClass(string s, SqlDbType e)
{ System.Console.WriteLine("{0} => TestClass(Enum SqlDbType)", s); }
}
// This is perfectly valid:
SqlDbType valid = 0;
// Whilst this is not:
SqlDbType ohNoYouDont = 1;
var a1 = new TestClass("0", 0);
// 0 => TestClass(Enum SqlDbType)
var a2 = new TestClass("1", 1);
// => 1 => TestClass(object)
(адаптировано из Visual С# 2008 Breaking Changes - изменить 12)
Когда компилятор выполняет разрешение перегрузки, 0 является Применимым элементом функции для конструкторов SqlDbType
и object
, потому что:
существует неявное преобразование (раздел 6.1) из типа аргумента в тип соответствующего параметра
(И SqlDbType x = 0
и object x = 0
действительны)
Параметр SqlDbType
лучше, чем параметр object
из-за лучших правил преобразования:
- Если
T1
и T2
являются одним и тем же типом, ни одно преобразование не лучше.
-
object
и SqlDbType
не относятся к типу
- Если
S
T1
, C1
является лучшим преобразованием.
- Если
S
T2
, C2
является лучшим преобразованием.
- Если существует неявное преобразование от
T1
до T2
, и не существует никакого неявного преобразования от T2
до T1
, C1
является лучшим преобразованием.
- Нет неявного преобразования из
object
в SqlDbType
существует
- Если существует неявное преобразование от
T2
до T1
, и не существует никакого неявного преобразования от T1
до T2
, C2
является лучшим преобразованием.
- Существует неявное преобразование из
SqlDbType
в object
, поэтому SqlDbType
является лучшим преобразованием
Обратите внимание, что то, что точно составляет константу 0, (довольно тонко) изменилось в Visual С# 2008 (реализация Microsoft спецификации С#), поскольку @Eric объясняет в своем ответе.
Ответ 2
Ответ РичардаТоувера превосходный, но я думал, что добавлю немного к нему.
Как указывали другие ответы, причиной такого поведения является (1) ноль, конвертируемый в любое перечисление, и, очевидно, для объекта, и (2) любой тип перечисления более специфичен для этого объекта, поэтому метод, который принимает поэтому перечисление выбирается с помощью разрешения перегрузки как лучшего метода. Пункт второй - я надеюсь, что я сам объясню, но что объясняет первый пункт?
Во-первых, здесь есть неудачное отклонение от спецификации. В спецификации указано, что любой буквальный ноль, т.е. Число 0
, фактически буквально отображающееся в исходном коде, может быть неявно преобразовано в любой тип перечисления. Компилятор фактически реализует, что любой константный ноль может быть преобразован таким образом. Причина этого - из-за ошибки, из-за которой компилятор иногда допускает постоянные нули, а иногда нет, странным и непоследовательным образом. Самый простой способ решить проблему состоял в том, чтобы последовательно допускать постоянные нули. Вы можете прочитать об этом подробнее здесь:
http://blogs.msdn.com/b/ericlippert/archive/2006/03/28/the-root-of-all-evil-part-one.aspx
Во-вторых, причина, позволяющая конвертировать нули в любое перечисление, заключается в том, чтобы всегда можно было обнулить "флажок" перечисления. Хорошая практика программирования заключается в том, что каждое перечисление "flags" имеет значение "None", которое равно нулю, но это правило, а не требование. Дизайнеры С# 1.0 думали, что это выглядит странно, что вам, возможно, придется сказать
for (MyFlags f = (MyFlags)0; ...
для инициализации локального. Мое личное мнение заключается в том, что это решение вызвало больше проблем, чем это стоило, как с точки зрения печали над вышеупомянутой ошибкой, так и с точки зрения странностей, которые она вносит в обнаружение перегрузки, которое вы обнаружили.
Наконец, дизайнеры конструкторов могли понять, что это было бы проблемой в первую очередь, и сделал сигнатуры перегрузок, чтобы разработчик мог четко решить, какой ctor вызвать, не вставляя роли. К сожалению, это довольно неясная проблема, и поэтому многие дизайнеры не знают об этом. Надеюсь, кто-нибудь, кто читает это, не допустит ошибку; не создают двусмысленности между объектом и любым перечислением, если вы намерены, чтобы два переопределения имели разную семантику.
Ответ 3
Это, по-видимому, известное поведение и влияет на любые перегрузки функций, где есть как перечисление, так и тип объекта. Я все это не понимаю, но Эрик Липперт довольно хорошо подвел итоги своего блога
Ответ 4
Это вызвано тем, что целочисленный литерал 0 имеет неявное преобразование в любой тип перечисления. Спецификация С# гласит:
6.1.3. Неявные преобразования перечисления
Неявное преобразование перечислений допускает десятичное целое число-литерал 0 для преобразования в любой тип перечисления и любой тип с нулевым значением, базовый тип - это перечисляемый тип. В последнем случае преобразование оценивается путем преобразования в базовый тип перечисления и обертывания результат.
В результате наиболее конкретной перегрузкой в этом случае является SqlParameter(string, DbType)
.
Это не относится к другим значениям int, поэтому конструктор SqlParameter(string, object)
является наиболее конкретным.
Ответ 5
При разрешении типа для перегруженного метода С# выбирает наиболее конкретный вариант. Класс SqlParameter имеет два конструктора, которые принимают ровно два аргумента, SqlParameter(String, SqlDbType)
и SqlParameter(String, Object)
. Когда вы предоставляете литерал 0
, его можно интерпретировать как объект или как SqlDbType. Поскольку SqlDbType более специфичен, чем Object, предполагается, что это намерение.
Вы можете узнать больше о разрешении перегрузки в этом ответе.