Вывод типа C # Приводит к получению неправильного типа
Вопрос
Я создал следующее свойство, которое выдавало InvalidCastException
если к получателю был получен доступ, когда ViewState[TOTAL_RECORD_COUNT]
был null
.
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Моя мысль заключается в том, что он неправильно попытался распаковать объект в ViewState[TOTAL_RECORD_COUNT]
к одному int
, который потерпел неудачу , потому что он содержал long
, но я думаю, что в этой логике может быть изъян.Я оставлю это читателю в качестве упражнения, чтобы указать на этот недостаток.
С тех пор я изменил это свойство на read
public long TotalRecordCount
{
get { return (long?)ViewState[TOTAL_RECORD_COUNT] ?? -1; }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
который работает просто великолепно.Тем не менее, мне остается гадать, что было не так с моей первоначальной версией...StackOverflow спешит на помощь?
Обратите внимание, что если я попытаюсь выполнить (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1)
в немедленном окне я получаю сообщение об ошибке Cannot unbox 'ViewState[TOTAL_RECORD_COUNT] ?? -1' as a 'long'
и если я выполню (ViewState[TOTAL_RECORD_COUNT] ?? -1).GetType().Name
Я получаю Int32
.Я могу казнить (long)-1
и в конечном итоге с -1 в качестве Int64
... так в чем дело?
Решение
Возвращаемый тип ViewState
индексатор - это Object
(Я полагаю, вы имеете в виду ASP.NET viewstate здесь).Теперь рассмотрим, что должен сделать компилятор, когда он видит это (что эквивалентно вашему коду).:
object o = ViewState[...];
var x = o ?? -1;
Он должен вывести тип результата выражения o ?? -1
каким-то образом.Слева он видит object
, справа находится int
.Очевидно, что наиболее общим типом для этого выражения также является object
.Однако это означает, что если это действительно заканчивается использованием этого -1
(потому что o
было null), ему придется преобразовать его в object
- и для int
, это означает бокс.
Итак x
относится к типу object
, и он может содержать int
(и, возможно, также какой-то другой интегральный тип - мы не знаем, что находится в вашем viewstate, это может быть short
, например).Теперь вы пишете:
long y = (long)x;
С тех пор как x
является object
, это распаковка коробки.Однако вы можете распаковывать типы значений только в точно такой же тип (с единственным исключением, заключающимся в том, что вы можете заменить подписанный тип эквивалентным неподписанным типом и перечислить его базовый тип).То есть вы не можете распаковать коробку int
в long
.Гораздо более простым способом воспроизвести это, без "дополнительного" кода, было бы:
object x = 123;
long y = (long)x;
Который также бросает InvalidCastException
, и точно по той же причине.
Другие советы
Бросок должен быть всего в один шаг.
Выражение <object> ?? <int>
создаст другой объект, и когда первое значение равно null, т.е. ViewState[TOTAL_RECORD_COUNT]
равно null, тогда результирующим значением будет объект с вложенным в него значением Int32.
Поскольку вы не можете распаковать объект, содержащий Int32, в long, вам нужно сначала распаковать его в Int32, а затем преобразовать в long.
Проблемы заключаются не в распаковке ViewState[TOTAL_RECORD_COUNT]
, проблема заключается в упаковке и распаковке -1.
ViewState[TOTAL_RECORD_COUNT] ?? -1
Вы используете ??оператор для "object" и "int".Результирующий тип - "object".Это означает, что значение -1 будет помещено в рамку (как int), когда поле не существует в состоянии просмотра.
Затем ваша программа завершает работу позже, когда она пытается распаковать (int)-1 как long.
В вашем оригинале, если вы разберете это, вы делали:
(ViewState[TOTAL_RECORD_COUNT] ?? -1)
В оператор объединения нулей (??) специально разработан для:
чтобы определить значение по умолчанию для типы значений с возможностью обнуления а также , как ссылочные типы.
В вашем случае вы используете его для обработки System.Object, поэтому он примет ваше "-1", обработает его как Int32 и поместит в новую систему.Object.Затем он пытается распаковать Int32 в long, что завершается неудачей, поскольку приведение не может распаковать и изменить тип за один шаг.
Вы можете легко решить эту проблему, указав, что ваше значение -1 равно long , используя суффикс L:
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1L); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Int64 является типом значения, поэтому приведение null
к типу значения всегда будет генерироваться исключение (NullReferenceException
).И преобразование Int32 в Int64 будет успешным и не приведет к InvalidCastException
.