C # Tipo Inference Obtém o tipo errado
Pergunta
Eu criei a seguinte propriedade, que jogou um InvalidCastException
se o getter foi acessado quando ViewState[TOTAL_RECORD_COUNT]
foi null
.
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Meu pensamento é que ele tentou incorretamente para unbox o objeto em ViewState[TOTAL_RECORD_COUNT]
a um int
, que falhou porque continha um long
, mas eu acho que pode haver uma falha em que a lógica. Vou deixar isso como um exercício para o leitor a apontar essa falha.
Eu tenho mudado desde que a propriedade para ler
public long TotalRecordCount
{
get { return (long?)ViewState[TOTAL_RECORD_COUNT] ?? -1; }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
que funciona apenas inchar. Ainda assim, eu fiquei me perguntando o que estava errado com a minha versão original ... StackOverflow para o resgate?
Note que, se eu tentar executar (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1)
na janela imediata, fico com a Cannot unbox 'ViewState[TOTAL_RECORD_COUNT] ?? -1' as a 'long'
mensagem de erro e se eu executar (ViewState[TOTAL_RECORD_COUNT] ?? -1).GetType().Name
eu recebo Int32
. Posso executar (long)-1
e acabar com -1 como um Int64
... então qual é?
Solução
O tipo de retorno do indexador ViewState
é Object
(presumo que você quer dizer ASP.NET viewstate aqui). Agora considere o que o compilador tem que fazer quando se vê isso (que é equivalente ao seu código):
object o = ViewState[...];
var x = o ?? -1;
Tem de deduzir o tipo de resultado de o ?? -1
expressão de alguma forma. À esquerda vê uma object
, à direita é um int
. Claramente, o tipo mais geral para esta expressão é também object
. No entanto, isto significa que se ele realmente acaba usando esse -1
(porque o
foi nula), ele terá de convertê-lo para object
-. E por um int
, isso significa boxe
Assim x
é do tipo object
, e pode conter um int
(e talvez também algum outro tipo integral - não sabemos o que está na sua viewstate, poderia ser short
, por exemplo). Agora você escreve:
long y = (long)x;
Desde x
é object
, este é unboxing. No entanto, você só pode tipos de valor Unbox em mesmo tipo exato (com a única exceção é que você pode substituir um tipo assinado para um tipo não assinado equivalente, e enum para seu tipo base subjacente). Ou seja, você não pode int
unbox em long
. Uma maneira muito mais simples de repro isso, sem código "extra", seria:
object x = 123;
long y = (long)x;
O que também joga InvalidCastException
, e para exatamente a mesma razão.
Outras dicas
Um elenco tem que ser uma etapa única.
O <object> ?? <int>
expressão produzirá um outro objecto, e quando o primeiro valor é nula, isto é. ViewState[TOTAL_RECORD_COUNT]
é nulo, então o valor resultante será um objeto, com um Int32 boxed nele.
Uma vez que você não pode unbox um objeto contendo um Int32 para um longo, você precisa primeiro unbox-lo para um Int32, e depois lançá-lo para um longo.
Os problemas não é o unboxing do ViewState[TOTAL_RECORD_COUNT]
, o problema é o boxing e unboxing do -1.
ViewState[TOTAL_RECORD_COUNT] ?? -1
Você está usando o ?? operador em "objeto" e "int". O tipo resultante é "objecto". Isto significa que a -1 são embalados (como int) quando o campo não existe no estado de exibição.
Em seguida, o programa trava mais tarde, quando ele tenta unbox o (int) -1 como um longo.
Em seu original, se você quebrá-lo para baixo, você estava fazendo:
(ViewState[TOTAL_RECORD_COUNT] ?? -1)
O nula-coalescência operador (??) é specifially projetado para:
para definir um valor padrão para um tipo de valor anulável , bem como tipos de referência .
No seu caso, você está usando-o para lidar com uma System.Object, por isso vai levar o seu "-1", tratá-lo como um Int32, e caixa-lo em um novo System.Object. Em seguida, ele tenta unbox o Int32 em uma longa, que falha, uma vez que o elenco não pode unbox e altere o tipo em uma única etapa.
Você pode resolver isso facilmente, especificando que o seu -1 é um longo usando o sufixo L:
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1L); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Int64 é um tipo de valor, então lançando null
para um tipo de valor será sempre lançar uma exceção (NullReferenceException
). E lançando um Int32 para Int64 terá sucesso e não irá lançar uma InvalidCastException
.