2011-02-01 17 views
10

Recentemente il mio collega mi ha mostrato un blocco di codice che non funzionava correttamente:C operatore # nullo coalescenza ritorno nullo

public class SomeClass 
{ 
    private IList<Category> _categories; 

    public void SetCategories() 
    { 
     _categories = GetCategories() ?? new List<Category>(); 
     DoSomethingElse(); 
    } 

    public IList<Category> GetCategories() 
    { 
     return RetrieveCategories().Select(Something).ToList(); 
    } 
} 

(Sono consapevole del fatto che l'operatore è superfluo in quanto il ToList LINQ restituirà sempre un lista, ma è così che è stato impostato il codice).

Il problema era che _categories era null. Nel debugger, impostando un punto di interruzione su _categories = GetCategories() ?? new List<Category>(), quindi passando a DoSomethingElse(), le categorie _ sarebbero ancora nulle.

L'impostazione diretta di _categories su GetCategories() ha funzionato correttamente. Dividere il ?? in una dichiarazione completa se funzionava bene. L'operatore null coalescente no.

È un'applicazione ASP.NET, quindi un thread diverso potrebbe interferire, ma era sulla sua macchina, con solo lui connesso in un browser. _cateogories non è statico o nulla.

Quello che mi chiedo è, come è possibile che ciò accada?

Edit:

solo per aggiungere alla bizzarria, _categories è mai messo da nessuna parte, oltre che la funzione (a parte l'inizializzazione della classe).

il codice esatto, è in questo modo:

public class CategoryListControl 
{ 
    private ICategoryRepository _repo; 
    private IList<Category> _categories; 

    public override string Render(/* args */) 
    { 
     _repo = ServiceLocator.Get<ICategoryRepository>(); 
     Category category = _repo.FindByUrl(url); 
     _categories = _repo.GetChildren(category) ?? new List<Category>(); 
     Render(/* Some other rendering stuff */); 
    } 
} 

public class CategoryRepository : ICategoryRepository 
{ 
    private static IList<Category> _categories; 

    public IList<Category> GetChildren(Category parent) 
    { 
     return _categories.Where(c => c.Parent == parent).ToList<Category>(); 
    } 
} 

Anche lo GetChildren magicamente restituito un valore nullo, CategoryListControl._categories ancora non dovrebbero mai, mai essere nullo. GetChildren non dovrebbe mai restituire mai null a causa di IEnumerable.ToList().

Edit 2:

Provando codice di @ smartcaveman, ho scoperto questo:

Category category = _repo.FindByUrl(url); 

_categories = _repo.GetChildren(category) ?? new List<Category>(); 

_skins = skin; // When the debugger is here, _categories is null 

Renderer.Render(output, _skins.Content, WriteContent); // When the debugger is here, _categories is fine. 

Così, quando si prova if(_categories == null) throw new Exception(), _categories è stato nullo sulla if, quindi scavalcando l'eccezione era non gettato.

Quindi, sembra che questo sia un bug di debugger.

+0

È perché si sta creando un'istanza nell'istruzione? Forse bene, ma non l'ho mai usato prima. –

+0

Potresti controllare di nuovo? Sto pensando "errore di debug". –

+2

La parte affascinante qui è che 'GetCategories()' non è in grado di restituire 'null' in ogni caso. L'operatore coalescente è comunque inutile. –

risposta

2

Questo potrebbe essere un problema con il debugger piuttosto che con il codice. Provare a stampare il valore o eseguire un controllo Null dopo l'istruzione con l'operatore di coalesce.

+0

Credo che il programma abbia lanciato una NullReferenceException in primo luogo, che lo ha portato ad allegare il debugger ea vedere quel problema . Suppongo che sia ancora possibile, però. – Snea

+0

Immagino di essere stato scambiato per una NullReferenceException. Un controllo per null restituirà che il valore non è nullo, anche se quando il debugger era direttamente sull'istruzione if, mostrava che _categories era nullo. – Snea

1

Se si è sicuri di essere a causa di problemi di threading, utilizzare la parola chiave lock. Credo che questo dovrebbe funzionare.

public class SomeClass 
{ 
    private IList<Category> _categories; 

    public void SetCategories() 
    { 
     lock(this) 
     { 
      _categories = GetCategories() ?? new List<Category>(); 
      DoSomethingElse(); 
     } 
    } 

    public IList<Category> GetCategories() 
    { 
     return RetrieveCategories().Select(Something).ToList(); 
    } 
} 
1

Provare a fare una costruzione pulita. Costruisci il menu-> pulisci, quindi esegui di nuovo il debug. Il codice stesso va bene.

+1

Hmmmm. Questo potrebbe essere dovuto a file binari stantii? +1 –

+0

Questo è il mio sospetto. –

+0

È possibile, ma abbiamo modificato il codice in una istruzione manuale if, che ha funzionato, quindi è tornata e ha smesso di funzionare nuovamente. I file binari non aggiornati potrebbero inoltre far sì che il debugger si lamenti del fatto che la fonte sia diversa? Vedrò se possiamo riprodurlo di nuovo oggi. – Snea

2

L'operatore a coalescenza nulla non è danneggiato. Lo uso in modo simile tutto il tempo abbastanza con successo. Qualcos'altro sta succedendo.

1

(1) DoSomethingElse() potrebbe essere l'impostazione del campo _categories su null, prima che venga visualizzato l'errore. Un modo per testare questo è rendere il campo _categories in sola lettura.Se questo è l'errore, verrà visualizzato un errore del compilatore che un campo di sola lettura non può essere utilizzato come destinazione di assegnazione.
(2) Il campo _categorie viene impostato tramite un'altra funzione in un thread diverso. In entrambi i casi, il seguente dovrebbe risolvere il tuo problema, o almeno chiarire dove si trova.

public class SomeClass 
{ 
    private static readonly object CategoryListLock = new object(); 
    private readonly List<Category> _categories = new List<Category>(); 
    private bool _loaded = false; 

    public void SetCategories() 
    { 
     if(!_loaded) 
     { 
      lock(CategoryListLock) 
      { 
       if(!_loaded) 
       { 
        _categories.AddRange(GetCategories()); 
        _loaded = true; 
       } 
      } 
     } 
     DoSomethingElse(); 
    } 

    public IList<Category> GetCategories() 
    { 
     return RetrieveCategories().Select(Something).ToList(); 
    } 
} 

** Dopo aver visto la tua modifica, sembra che si dispone di due campi differenti che sono IList<Category> _categories. Non ha senso che il campo _categories nello CategoryListControl sia nullo, ma lo statico _categories nella classe CategoryRepository sembra essere nullo in base a ciò che è stato pubblicato. Forse ti stai confondendo su quale campo stia generando l'errore. Comprendo che la riga viene chiamata in CategoryListControl, quindi il tuo errore dirà che si trova nella classe CategoryListControl, ma l'eccezione effettiva potrebbe essere dal metodo GetChildren(), che tenta di creare un elenco bambini da un elenco Null). Poiché i campi hanno lo stesso nome, è facile vedere come potrebbero confondersi. Verificare questo rendendo il campo _categories nel campo CategoryRepository un campo inizializzato di sola lettura.

Anche se il campo _categories nel CategoryRepository non è sempre nullo, potrebbe essere soggetto ad alcuna delle preoccupazioni threading che ho spiegato come risolvere per la classe di controllo **

Itto assicurarsi che si sta eseguendo il debug del correggi il campo _categories, prova questo.

_categories = GetCategories() ?? new List<Category>(); 
    if(_categories == null){ 
      throw new Exception("WTF???"); 
    } 
    DoSomethingElse(); 

Se non si ottiene l'eccezione con "WTF ???" allora sai che la fonte dell'errore è altrove.

E, per quanto riguarda le estensioni Linq: Né Where() né ToList() possono restituire null. Entrambi i metodi generano una ArgumentNullException se tutti i parametri sono nulli. Ho controllato questo con il riflettore.

Facci sapere quali risultati ottieni con questo. Sono curioso anche adesso.

+0

Sfortunatamente, '_categories' è _solo_ impostato da quella funzione. – Snea

+0

@smartcaveman, sono abbastanza sicuro che siano le _categories in CategoryListControl e che a causa di ciò venga lanciata un'eccezione. Nel debugger, possiamo passare sopra la linea operatore coalescente nulla senza un'eccezione, e nella riga successiva CategoryListControl._categories è nullo. Se CategoryRepository._cateogries era nullo, la riga operatore di coaxlescing null genererebbe un'eccezione su Where(), poiché l'input non può essere nullo. In entrambi i casi, la lista degli utenti in GetCategories non può restituire null, vero? – Snea

+0

@smartcaveman, controlla la modifica. Provare il tuo codice sembra indicare un problema di debugger. Ho accettato la risposta di Tim H perché è tecnicamente ciò che ritengo sia il problema, ma accetterei anche il tuo se potessi. Grazie. – Snea

1

Questo potrebbe accadere perché si sono attivate le ottimizzazioni - in tal caso l'assegnazione può essere ritardata fino a quando il compilatore può dimostrare che così facendo non cambia il risultato. Certo, questo sembra strano nel debugger, ma è perfettamente OK.

Problemi correlati