Подтвердить что ты не робот

Значение типа компилятора Разрешение и жестко заданные значения "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?

4b9b3361

Ответ 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 является лучшим преобразованием.
    • 0 не является object
  • Если S T2, C2 является лучшим преобразованием.
    • 0 не является SqlDbType
  • Если существует неявное преобразование от 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, предполагается, что это намерение.

Вы можете узнать больше о разрешении перегрузки в этом ответе.