2016-03-09 16 views
16

Mi aspettavo che funzionasse, ma apparentemente il modo in cui l'IL genera, genera NullReferenceException. Perché il compilatore non può generare un codice simile per le query?L'operatore condizionale nullo C# 6 non funziona per la query LINQ

Nel caso ThisWorks, il compilatore genera codice che cortocircuita il resto dell'espressione, perché non può fare la stessa cosa per il caso di query LINQ?

class Target 
{ 
    public ChildTarget Child; 
} 

class ChildTarget 
{ 
    public int[] Values; 
} 

IEnumerable<int> ThisWorks(Target target) => 
    target.Child?.Values.Select(x => x); 

IEnumerable<int> ThisDoesNotWork(Target target) => 
    from x in target.Child?.Values select x; 

ThisWorks(new Target()); 
ThisDoesNotWork(new Target()); // this throws NullReferenceException 

decompilato risultati

private static IEnumerable<int> ThisDoesNotWork(Target target) 
{ 
    ChildTarget child = target.Child; 
    IEnumerable<int> values = (child != null) ? child.Values : null; 
    Func<int, int> func; 
    if ((func = Program._func) == null) 
    { 
     func = (Program._func = new Func<int, int>(Program._funcMethod)); 
    } 
    return values.Select(func); 
} 

private static IEnumerable<int> ThisWorks(Target target) 
{ 
    ChildTarget child = target.Child; 
    IEnumerable<int> values; 
    if (child == null) 
    { 
     values = null; 
    } 
    else 
    { 
     IEnumerable<int> values = child.Values; 
     Func<int, int> func; 
     if ((func = Program._func2) == null) 
     { 
      func = (Program._func2= new Func<int, int>(Program._funcMethod2)); 
     } 
     values = values.Select(func); 
    } 
    return values; 
} 
+0

La mia ipotesi è che il compilatore traduca l'operatore condizionale nullo prima di tradurre la sintassi della query. Quindi la tua query è come '(Child == null? Null: Child.Values) .Select (x => x)'. Se avesse tradotto la sintassi della query sulla sintassi del metodo prima avrebbe funzionato. – juharr

+0

Perché ti aspetteresti che si comporti diversamente? Nel tuo esempio, 'target.Child? .Values' valuta a' null'. Il condizionale nullo influenza solo l'espressione di cui fa parte. Stai facendo effettivamente 'null.Select (...)'. –

+0

@JeffMercado Mi aspetto 'da x in e? .Value selezionare x' per essere' e? .Value.Select (x = x>) '* poiché * l'espressione successiva * funziona * questa è la sorpresa. Come ha fatto notare @Neal, è la parentesi sottile a dare la colpa, così come l'ordine delle trasformazioni –

risposta

9

La risposta è nella specifica del linguaggio C#, che dice

un'espressione di query della forma

da x in e selezionare x

viene tradotto in

(e). Selezionare (x =>x)

Nota le parentesi intorno e nell'ultima riga. Ciò mostra chiaramente che l'espressione condizionale nulla (nel tuo esempio) finisce prima che venga chiamato Select, il che significa che Select potrebbe essere chiamato con il risultato nullo.

Perché non può fare la stessa cosa per Linq? Perché non è così che la funzione è stata progettata per funzionare. Le specifiche per gli operatori null-condizionali non hanno un caso speciale per le query, né viceversa.

+0

stavo pianificando di trovare la specifica per l'operatore condizionale nullo per completare questa spiegazione, solo per scoprire che non ce n'era. Ora dovremo dedicare un po 'di tempo ad analizzare questo thread per capire cosa è successo: https://roslyn.codeplex.com/discussions/540883 –

+0

Inoltre confermato '(target.Child? .Values) .Seleziona (x => x) 'butta anche, come previsto. –