2009-10-01 3 views
2

J'ai créé la propriété suivante, qui a généré un InvalidCastException si le getter a été accédé lorsque ViewState[TOTAL_RECORD_COUNT] était null.Inférence de type C# obtient le type incorrect

public long TotalRecordCount 
{ 
    get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1); } 
    set { ViewState[TOTAL_RECORD_COUNT] = value; } 
} 

Ma pensée est que mal tenté de unbox l'objet ViewState[TOTAL_RECORD_COUNT] à un int, qui a échoué parce qu'elle contenait une long, mais je pense qu'il pourrait y avoir une faille dans cette logique. Je vais laisser au lecteur le soin de signaler ce défaut.

J'ai depuis changé cette propriété pour lire

public long TotalRecordCount 
{ 
    get { return (long?)ViewState[TOTAL_RECORD_COUNT] ?? -1; } 
    set { ViewState[TOTAL_RECORD_COUNT] = value; } 
} 

qui travaille la houle juste. Pourtant, je me suis demandé ce qui n'allait pas avec ma version originale ... StackOverflow à la rescousse?

Notez que si je tente d'exécuter (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1) dans la fenêtre immédiate, je reçois le message d'erreur Cannot unbox 'ViewState[TOTAL_RECORD_COUNT] ?? -1' as a 'long' et si j'exécute (ViewState[TOTAL_RECORD_COUNT] ?? -1).GetType().Name je reçois Int32. Je peux exécuter (long)-1 et finir avec -1 comme Int64 ... alors quoi de neuf?

+0

Mettez cette valeur en session! Laissez ViewState uniquement pour l'interface utilisateur, mec. – Randolpho

+3

Cela pourrait vous aider à comprendre ce qui se passe ici. http://blogs.msdn.com/ericlippert/archive/2009/03/19/representation-and-identity.aspx –

+0

La session n'est pas conviviale, d'ailleurs. –

Répondre

13

Le type de retour de l'indexeur ViewState est Object (je suppose que vous voulez dire ASP.NET viewstate ici). Considérons maintenant ce que le compilateur doit faire quand il voit (ce qui équivaut à votre code):

object o = ViewState[...]; 
var x = o ?? -1; 

Il doit déduire le type de résultat de l'expression o ?? -1 en quelque sorte. Sur la gauche, il voit un object, sur la droite est un int. Clairement, le type le plus général pour cette expression est également object. Cependant, cela signifie que si elle finit par utiliser cette -1 (parce que o était nul), il faudra le convertir en object - et pour un int, cela signifie la boxe.

Alors x est de type object, et il peut contenir un int (et peut-être aussi un autre type intégral - nous ne savons pas ce qui est dans votre état d'affichage, il pourrait être short, par exemple). Maintenant, vous écrivez:

long y = (long)x; 

Depuis x est object, c'est unboxing. Cependant, vous ne pouvez que déballer les types de valeur dans le même type (à la seule exception près que vous pouvez substituer un type signé pour un type non-signé équivalent, et enum pour son type de base sous-jacent). C'est-à-dire que vous ne pouvez pas déballer int en long. Une façon beaucoup plus simple de repro cela, sans code « extra », serait:

object x = 123; 
long y = (long)x; 

qui jette également InvalidCastException, et pour cette raison exacte.

+0

Merci pour l'explication claire et complète. Je vais dormir beaucoup mieux ce soir! –

0

Int64 est un type de valeur. Par conséquent, la conversion null en type valeur lancera toujours une exception (NullReferenceException). Et lancer un Int32 à Int64 réussira et ne lancera pas un InvalidCastException.

+0

A moins que je ne manque quelque chose, il n'essaie pas de faire de la nullité? Il a l'opérateur de coalescence nulle à la fin. –

+0

Je pense que vous avez besoin de lire le code un peu plus près ... le casting réel qui échoue tente de lancer -1 à un Int64 –

1

Dans l'original, si vous cassez vers le bas, vous faites:

(ViewState[TOTAL_RECORD_COUNT] ?? -1) 

Le null-coalescing operator (??) est specifially conçu pour:

pour définir une valeur par défaut pour un type de valeur nullables ainsi que les types de référence .

Dans votre cas, vous l'utilisez pour gérer un System.Object, donc il va prendre votre « -1 », le traiter comme un Int32, et la boîte dans une nouvelle System.Object. Ensuite, il essaye de décompacter Int32 en un long, ce qui échoue, puisque la distribution ne peut pas déballer et changer le type en une seule étape.

Vous pouvez résoudre facilement en précisant que votre -1 est un long en utilisant le suffixe L:

public long TotalRecordCount 
{ 
    get { return (long)(ViewState[TOTAL_RECORD_COUNT] ?? -1L); } 
    set { ViewState[TOTAL_RECORD_COUNT] = value; } 
} 
+0

Ce n'est pas le problème. En utilisant ?? avec un type de valeur serait une erreur de type compilation. – Daniel

+0

L'utilisation de '??' avec un type de valeur non nullable sur le côté gauche entraînerait cependant une erreur de compilation. En outre, le type de retour de 'ViewState.this []' est 'object' qui est évidemment un type de référence. –

1

Les problèmes n'est pas le unboxing du ViewState[TOTAL_RECORD_COUNT], le problème est la boxe et unboxing du -1.

ViewState[TOTAL_RECORD_COUNT] ?? -1 

Vous utilisez le ?? opérateur sur "objet" et "int". Le type résultant est "objet". Cela signifie que -1 sera encadré (comme int) lorsque le champ n'existe pas dans l'état d'affichage.

Ensuite, votre programme se bloque plus tard quand il essaie de décompacter le (int) -1 comme un long.

5

Un plâtre doit être une seule étape.

L'expression <object> ?? <int> va produire un autre objet, et lorsque la première valeur est nulle, c'est-à-dire.ViewState[TOTAL_RECORD_COUNT] est null, alors la valeur résultante sera un objet, avec un Int32 encadré dedans.

Étant donné que vous ne pouvez pas déballer un objet contenant un Int32, vous devez d'abord le décocher sur un Int32, puis le convertir en un long.