C # Type Inference Ottiene il tipo sbagliato
Domanda
ho creato il seguente proprietà, che ha gettato un InvalidCastException
se il getter era accessibile quando ViewState[TOTAL_RECORD_COUNT]
era null
.
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Il mio pensiero è che erroneamente tentato di unboxing l'oggetto in ViewState[TOTAL_RECORD_COUNT]
a un int
, che non è riuscito perché conteneva un long
, ma credo che ci potrebbe essere un difetto in quella logica. Lascio come esercizio per il lettore a sottolineare che difetto.
Da allora ho cambiato proprietà per leggere
public long TotalRecordCount
{
get { return (long?)ViewState[TOTAL_RECORD_COUNT] ?? -1; }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
che funziona esattamente gonfiarsi. Eppure, io sono rimasto chiedo che cosa era sbagliato con la mia versione originale ... StackOverflow in soccorso?
Si noti che se provo ad eseguire (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1)
nella finestra immediata, ottengo il messaggio di errore Cannot unbox 'ViewState[TOTAL_RECORD_COUNT] ?? -1' as a 'long'
e se eseguo (ViewState[TOTAL_RECORD_COUNT] ?? -1).GetType().Name
ricevo Int32
. Posso eseguire (long)-1
e finire con -1 come Int64
... quindi cosa succede?
Soluzione
Il tipo di ritorno di ViewState
indicizzatore è Object
(presumo si intende ASP.NET viewstate qui). Consideriamo ora ciò che il compilatore ha a che fare quando si vede questo (che equivale al codice):
object o = ViewState[...];
var x = o ?? -1;
Si deve dedurre il tipo di risultato di espressione o ?? -1
in qualche modo. Sulla sinistra si vede un object
, sulla destra è un int
. Chiaramente, il tipo più generale di questa espressione è anche object
. Tuttavia, questo significa che se finisce in realtà per usare che -1
(perché o
era nullo), dovrà convertirlo in object
-. E per un int
, questo significa che la boxe
Quindi x
è di tipo object
, e può contenere un int
(e forse anche qualche altro tipo integrale - non sappiamo che cosa è nel vostro ViewState, potrebbe essere short
, per esempio). Ora si scrive:
long y = (long)x;
Dal x
è object
, questo è unboxing. Tuttavia, è possibile solo tipi di valore Unbox in esatta stesso tipo (con la sola eccezione che è possibile sostituire un tipo firmato per un tipo senza segno equivalente, e enum per il suo tipo di base sottostante). Cioè, non si può int
Unbox in long
. Un modo molto più semplice a Repro questo, senza codice "extra", potrebbe essere:
object x = 123;
long y = (long)x;
che getta anche InvalidCastException
, e per esattamente lo stesso motivo.
Altri suggerimenti
Un cast deve essere solo un passo.
Il <object> ?? <int>
espressione produrrà un altro oggetto, e quando il primo valore è nullo, cioè. ViewState[TOTAL_RECORD_COUNT]
è nullo, allora il valore risultante sarà un oggetto, con un Int32 scatolato in esso.
Poiché non è possibile unboxing un oggetto contenente un Int32 a un lungo, è necessario prima di unboxing ad un Int32, e poi gettato in un lungo.
Il problema non è l'unboxing del ViewState[TOTAL_RECORD_COUNT]
, il problema è la boxe e unboxing del -1.
ViewState[TOTAL_RECORD_COUNT] ?? -1
Si utilizza la ?? operatore "oggetto" e "int". Il tipo risultante è "oggetto". Ciò significa che l'-1 verrà imballata (come int) quando il campo non esiste nello stato di visualizzazione.
Poi il programma si blocca più tardi, quando si tenta di unboxing del (int) -1 come un lungo.
Nel vostro originale, se si scomposizione, si stava facendo:
(ViewState[TOTAL_RECORD_COUNT] ?? -1)
Il null coalescenza operatore (??) è specifially progettato per:
per definire un valore predefinito per tipi valore Null così come tipi di riferimento.
Nel tuo caso, lo si usa per gestire una System.Object, quindi sta andando a prendere il vostro "-1", trattarlo come un Int32, e la scatola in un nuovo System.Object. Poi, tenta di unboxing del Int32 in un lungo, che non riesce, dal momento che il cast non può unboxing e cambiare il tipo in un unico passaggio.
È possibile risolvere questo facilmente specificando che la vostra -1 è un lungo utilizzando il suffisso L:
public long TotalRecordCount
{
get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1L); }
set { ViewState[TOTAL_RECORD_COUNT] = value; }
}
Int64 è un tipo di valore, così colata null
per un tipo di valore sarà sempre un'eccezione (NullReferenceException
). Che esprimono un Int32 Int64 per riuscirà e non getterà un InvalidCastException
.