web-dev-qa-db-fra.com

opérateur conditionnel null ne fonctionne pas avec les types nullable?

J'écris un morceau de code en C # 6 et pour une raison étrange, cela fonctionne

var value = objectThatMayBeNull?.property;

mais cela ne veut pas:

int value = nullableInt?.Value;

Par ne fonctionne pas, je veux dire que j'obtiens une erreur de compilation en disant Cannot resolve symbol 'Value'. Toute idée pourquoi l'opérateur conditionnel nul ?. ne fonctionne pas?

61
linkerro

D'accord, j'ai réfléchi et testé. Voici ce qui se passe:

int value = nullableInt?.Value;

Donne ce message d'erreur lors de la compilation:

Le type "int" ne contient pas de définition pour "Value"

Cela signifie que ? 'Convertit' le int? En la valeur réelle int. C'est en fait la même chose que:

int value = nullableInt ?? default(int);

Le résultat est un entier, qui n'a pas de Value, évidemment.

D'accord, cela pourrait-il aider?

int value = nullableInt?;

Non, cette syntaxe n'est pas autorisée.

Et alors? Continuez simplement à utiliser .GetValueOrDefault() dans ce cas.

int value = nullableInt.GetValueOrDefault();
31
Patrick Hofman

La raison en est que l'accès à la valeur avec un opérateur conditionnel nul serait inutile:

  • Lorsque vous postulez x?.pp est un type de valeur non nullable T, le résultat est de type T?. De même, le résultat de nullableInt?.Value opération doit être nullable.
  • Quand ton Nullable<T> a une valeur, le résultat de nullableInt?.Value serait la même que la valeur elle-même
  • Quand ton Nullable<T> n'a pas de valeur, le résultat serait null, qui est, encore une fois, le même que la valeur elle-même.

Bien qu'il ne soit pas logique d'accéder à Value avec le ?., il est logique d'accéder à d'autres propriétés de types de valeur nullable. L'opérateur fonctionne de manière cohérente avec les types de valeur nullable et avec les types de référence, ces deux implémentations produisent donc un comportement identique:

class PointClass {
    public int X { get; }
    public int Y { get; }
    public PointClass(int x, int y) { X = x; Y = y; }
}
struct PointStruct {
    public int X { get; }
    public int Y { get; }
    public PointStruct(int x, int y) { X = x; Y = y; }
}
...
PointClass pc = ...
PointStruct? ps = ...
int? x = pc?.X;
int? y = ps?.Y;

Dans le cas d'un struct nullable, l'opérateur vous permet d'accéder à une propriété du type sous-jacent PointStruct, et il ajoute la nullité au résultat de la même manière qu'il le fait pour les propriétés non nullables du type de référence PointClass.

19
dasblinkenlight

En ce qui concerne les types nullables, l'opérateur ?. Dit if not null, use the wrapped value. Ainsi, pour un entier nullable, si la valeur nullable a la valeur 8, Le résultat de ?. Serait 8, Pas la valeur nullable qui contient 8. Puisque Value n'est pas une propriété d'un int, vous obtenez une erreur.

Ainsi, l'exemple d'essayer d'utiliser la propriété Value échoue à juste titre, mais ce qui suit fonctionnerait,

var x = nullableInt?.ToString();

Considérez l'opérateur de coalescence nulle, ??.

var x = nullableInt ?? 0;

Ici, l'opérateur dit, if null, return 0, otherwise return the value inside the nullable, Qui dans ce cas est un int. L'opérateur ?. Fonctionne de manière similaire en ce qui concerne l'extraction du contenu de la valeur Null.

Pour votre exemple spécifique, vous devez utiliser l'opérateur ?? Et une valeur par défaut appropriée plutôt que l'opérateur ?..

8
Jeff Yates

Je suis essentiellement d'accord avec les autres réponses. J'espérais juste que le comportement observé pourrait être soutenu par une certaine forme de documentation faisant autorité.

Étant donné que je ne peux trouver la spécification C # 6.0 nulle part (est-elle encore disponible?), Les plus proches de la "documentation" sont les Notes de conception de langage C # du 3 février 2014 . En supposant que les informations qui y figurent reflètent toujours la situation actuelle, voici les parties pertinentes qui expliquent formellement le comportement observé.

La sémantique revient à appliquer l'opérateur ternaire à une vérification d'égalité nulle, à un littéral nul et à une application non interrogée de l'opérateur, sauf que l'expression n'est évaluée qu'une seule fois:

e?.m(…)   =>   ((e == null) ? null : e0.m(…))
e?.x      =>   ((e == null) ? null : e0.x)
e?.$x     =>   ((e == null) ? null : e0.$x)
e?[…]     =>   ((e == null) ? null : e0[…])

e0 est identique à e, sauf si e est de type valeur nulle, auquel cas e0 est e.Value.

Appliquer cette dernière règle à:

nullableInt?.Value

... l'expression sémantiquement équivalente devient:

((nullableInt == null) ? null : nullableInt.Value.Value)

De toute évidence, nullableInt.Value.Value ne peut pas compiler, et c'est ce que vous avez observé.

Quant à savoir pourquoi la décision de conception a été prise pour appliquer cette règle spéciale spécifiquement aux types nullables, je pense que la réponse de dasblinkenlight couvre bien cela, donc je ne la répéterai pas ici.


De plus, je dois mentionner que, même si, hypothétiquement, nous n'avions pas cette règle spéciale pour les types nullables, et l'expression nullableInt?.Value a compilé et s'est comporté comme vous le pensiez à l'origine ...

// let's pretend that it actually gets converted to this...
((nullableInt == null) ? null : nullableInt.Value)

pourtant, la déclaration suivante de votre question serait invalide et produira une erreur de compilation:

int value = nullableInt?.Value; // still would not compile

La raison pour laquelle cela ne fonctionnerait toujours pas est que le type de nullableInt?.Value l'expression serait int?, pas int. Vous devrez donc changer le type de la variable value en int?.

Ceci est également formellement couvert dans les Notes de conception de langage C # du 3 février 2014 :

Le type du résultat dépend du type T du côté droit de l'opérateur sous-jacent:

  • Si T est (connu pour être) un type de référence, le type de l'expression est T
  • Si T est (connu pour être) un type de valeur non nullable, le type de l'expression est T?
  • Si T est (connu pour être) un type de valeur nullable, le type de l'expression est T
  • Sinon (c'est-à-dire si l'on ne sait pas si T est une référence ou un type de valeur), l'expression est une erreur de compilation.

Mais si vous étiez alors obligé d'écrire ce qui suit pour le compiler:

int? value = nullableInt?.Value;

... alors cela semble assez inutile, et ce ne serait pas différent de simplement faire:

int? value = nullableInt;

Comme d'autres l'ont souligné, dans votre cas, vous vouliez probablement utiliser opérateur de coalescence nulle ?? tout au long, pas le opérateur de condition nulle ?. .

5
sstan

int n'a pas de propriété Value.

Considérer:

var value = obj?.Property

Est équivalent à:

value = obj == null ? null : obj.Property;

Cela n'a aucun sens avec int et donc pas avec int? Via ?.

L'ancienne GetValueOrDefault() est cependant logique avec int?.

Ou d'ailleurs, puisque ? Doit retourner quelque chose de nullable, simplement:

int? value = nullableInt;
0
Jon Hanna

Tout simplement parce que (basé sur la réponse de Sstan ci-dessus)

var value = objectThatMayBeNull?.property;

est évalué par le compilateur comme

var value = (objectThatMayBeNull == null) ? null : objectThatMayBeNull.property

et

int value = nullableInt?.Value;

comme

int value = (nullableInt == null) ? null : nullableInt.Value.Value;

quand nullableInt.Value.Value est Cannot resolve symbol 'Value' erreur de syntaxe!

0
honzakuzel1989

L'opérateur conditionnel null déballe également la variable nullable. Donc, après le "?." , la propriété "Value" n'est plus nécessaire.

J'ai écrit un article qui va plus en détail sur la façon dont je suis tombé sur cela. Au cas où vous vous demandez

http://www.ninjacrab.com/2016/09/11/c-how-the-null-conditional-operator-works-with-nullable-types/

0
Min