2016-07-05 5 views
33

Je suis nouveau sur C# et tout en explorant les caractéristiques linguistiques, je suis tombé sur quelque chose d'étrange:Pourquoi C# nécessite-t-il des parenthèses lors de l'utilisation de valeurs nulles dans une expression?

struct Foo 
{ 
    public Foo Identity() { return this; } 

    public static void Bar(Foo? foo) 
    { 
     Foo foo1 = foo?.Identity().Value; // Does not compile 
     Foo foo2 = (foo?.Identity()).Value; // Compiles 
    } 
} 

Quelqu'un pourrait-il me expliquer pourquoi les parenthèses sont nécessaires?

+1

Dans le premier cas, vous essayez d'accéder à un membre nommé '' value' dans foo', qui n'existe pas. Dans la deuxième instruction, 'Value' fait référence à une propriété de' Nullable '. – xfx

+9

Si vous y pensez vraiment, appeler '.Value' sur une expression qui inclut l'opérateur conditionnel nul est contradictoire (soit vous attendez une valeur nulle, soit vous ne le faites pas).Vous voudrez probablement utiliser un opérateur de coalescence nul à la place, auquel cas les parenthèses ne sont pas nécessaires. Par exemple: 'Foo foo2 = foo? .Identity() ?? '; – sstan

Répondre

41

Quelqu'un pourrait-il m'expliquer pourquoi les parenthèses sont nécessaires?

Because Identity() renvoie un Foo (pas Foo?) et n'a donc pas la propriété Value. Si foo est null, la valeur null sera propagée via l'appel Identity.

Lorsque vous mettez entre parenthèses autour d'elle, les résultats de l'expression est un Nullable<Foo> qui fait ont une propriété Value.

Notez également que si fooest nulle, alors vous serez appeler Value sur un Nullable<Foo> qui n'a pas de valeur, et aurez une exception lors de l'exécution. Certains analyseurs statiques reconnaîtront que vous avez une exception de référence nulle possible et vous préviendront.

Si vous les étendre à leurs équivalents sans nul propagation, il sera plus clair:

Foo foo1; 
if(foo != null) 
{ 
    foo1 = foo.Identity().Value; // not possible - Foo has no Value property. 
} 
else 
{ 
    foo1 = null; // also not possible 
} 

Foo foo2; 
Foo? temp; 
if(foo != null) 
{ 
    temp = foo.Identity(); 
} 
else 
{ 
    temp = null; // actually a Nullable<Foo> with no value 
} 
foo2 = temp.Value; // legal, but will throw an exception at run-time if foo is null 

Si Identity() retours Foo, pourquoi ne pas Foo foo3 = foo?.Identity(); compilez?

L'équivalent de ce serait:

Foo foo3 
if(foo != null) 
{ 
    foo3 = foo.Identity(); 
} 
else 
{ 
    foo3 = null; // not possible 
} 
+0

Les joies d'utiliser le même symbole pour désigner différents opérateurs. – BoltClock

+0

Vous avez gagné contre mon ordinateur et mon réseau très lents :) –

+0

Si 'Identity()' retourne 'Foo', pourquoi est-ce que' Foo foo3 = foo? .Identity(); 'ne compile pas? – Lukas92

0

Je pense que ce fut une bonne décision de l'équipe C# pour le faire de cette façon. Considérez le scénario ci-dessous:

Si la struct était:

struct Foo 
{ 
    public int ID { set; get; } 

    public Foo Identity() { return this; } 

    public static void Bar(Foo? foo) 
    { 
     int? foo1 = foo?.Identity().ID; // compile 
     Foo foo2 = (foo?.Identity()).Value; // Compiles 
    } 
} 

Si vous ne l'avez pas besoin de la parenthèse pour accéder au résultat Nullable, vous ne seriez pas en mesure d'accéder à la propriété ID. Étant donné que le ci-dessous ne compilera pas:

int? foo2 = (foo?.Identity()).GetValueOrDefault()?.ID 

Lorsque vous écrivez foo?.Identity(). ce qui est après la . est de type Foo retourné par Identity(). Cependant, dans (foo?.Identity()). ce qui est après la . est Foo? qui est le résultat réel de l'ensemble de la déclaration foo?.Identity().