2009-07-21 12 views
10

Devo controllare se una determinata chiave è presente nel dizionario se sono sicuro che verrà aggiunta nel dizionario quando raggiungo il codice per accedervi?Devo verificare se una determinata chiave è presente nel dizionario prima di accedervi?

Ci sono due modi posso accedere al valore nel dizionario

  1. controllo metodo ContainsKey. Se restituisce true, accedo utilizzando l'indexer [chiave] dell'oggetto dizionario.

o

  1. TryGetValue che restituirà vero o falso, così come valore di ritorno tramite il parametro fuori.

(2 ° si esibirà meglio di prima, se voglio ottenere il valore. Benchmark.)

Tuttavia, se sono sicuro che la funzione che accede dizionario globale avranno sicuramente la chiave allora dovrei comunque controllare usando TryGetValue o senza controllare dovrei usare indexer [].

Oppure non dovrei mai presumere questo e controllare sempre?

risposta

18

Utilizzare l'indicizzatore se la chiave deve essere presente; se non è presente, genererà un'eccezione appropriata, che è il comportamento corretto se l'assenza della chiave indica un bug.

Se è valido che la chiave non sia presente, utilizzare invece TryGetValue e reagire di conseguenza.

(Si applicano anche il consiglio di Marc su come accedere a un dizionario condiviso in modo sicuro.)

+1

Una chiave mancante è spesso un'indicazione di un altro problema. Se sai qual è il problema e puoi fornire un messaggio più utile, dovresti considerare di lanciare la tua eccezione poiché il messaggio che accompagna "KeyNotFoundException" è difficile da restringere a volte. –

+0

Sarebbe certamente utile se KeyNotFoundException desse anche la chiave - ma a parte questo, solo la traccia dello stack è normalmente sufficiente per me. Quanto lontano faresti questo: controllando ogni possibile riferimento null (non solo argomenti, ma cose che ti aspetti internamente di non-null) per nullità e lanciando un'eccezione personalizzata su ognuna di quelle? Ci saranno sempre delle eccezioni generate da frammenti del framework nei casi di incoerenza interna: se si tenta di anticipare ognuno di essi, il codice finisce per essere più relativo ai casi di errore che a fare progressi. –

10

Se il dizionario è globale (statico/condiviso), è necessario sincronizzarne l'accesso (questo è importante, altrimenti è possibile corromperlo).

Anche se il thread è solo lettura dati, è necessario rispettare i blocchi di altri thread che potrebbero modificarlo.

Tuttavia; se si è certi che l'oggetto è lì, l'indicizzatore dovrebbe andare bene:

Foo foo; 
lock(syncLock) { 
    foo = data[key]; 
} 
// use foo... 

In caso contrario, un modello utile è quello di verificare e aggiungere nello stesso blocco:

Foo foo; 
lock(syncLock) { 
    if(!data.TryGetValue(key, out foo)) { 
     foo = new Foo(key); 
     data.Add(key, foo); 
    } 
} 
// use foo... 

Qui abbiamo solo aggiungiamo la oggetto se non era lì ... ma all'interno della stessa serratura.

+0

Bello, ma non penso che quella fosse la domanda? Non vedo alcun riferimento alla concorrenza? :) – Thorarin

+0

"dizionario globale" - basta impostare un determinato percorso. Se la mia comprensione non è corretta, l'OP può semplicemente ignorare questa risposta ;-p –

+0

Perché è necessario bloccare il blocco durante la lettura dei dati dal dizionario? È dovuto all'espansione/reinghing del dizionario dopo qualche limite? –

2

Controllare sempre. Mai dire mai. Presumo che la tua applicazione non sia così critica dal punto di vista delle prestazioni da dover risparmiare tempo per il controllo.

SUGGERIMENTO: se si decide di non controllare, utilizzare almeno Debug.Assert (dict.ContainsKey (chiave)); Questo verrà compilato solo in modalità Debug, il tuo build di rilascio non lo conterrà. In questo modo potresti avere almeno il controllo durante il debug.

Ancora: se possibile, basta controllare :-)

EDIT: Ci sono state alcune idee sbagliate qui. Con "verifica sempre" non intendevo solo usare un se da qualche parte. Anche in questo caso è stata gestita correttamente un'eccezione. Quindi, per essere più precisi: non dare mai nulla per scontato, aspettarsi l'inaspettato. Controlla con ContainsKey o gestisci la potenziale eccezione, ma fai QUALCOSA nel caso in cui l'elemento non sia contenuto.

+4

Non sono d'accordo con questo. Se la chiave è pensata per essere presente e non lo è, il comportamento corretto è quello di lanciare un'eccezione, sì? E questo è esattamente ciò che fa l'indicizzatore - quindi perché non usare quel comportamento? Inoltre, non sono propenso a cambiare il comportamento tra debug e release build ... mi sembra una ricetta per i problemi. –

+0

Sì, il controllo debug non è stato concepito come best practice. MA è meglio che non controllare affatto. E lanciare un'eccezione nel caso in cui i dati debbano essere nel dizionario certo è la strada giusta da percorrere, ma probabilmente non è un'eccezione interna del dizionario, ma una che meglio si adatta al problema reale (ad esempio InvalidOperationException). – galaktor

+0

Sono d'accordo con Jon, tu controlli solo quando può esserci o non esserci, se sei sicuro che dovrebbe esserci, allora il percorso corretto è quello di permettere che l'eccezione venga lanciata e gestirla di conseguenza. – James

0

TryGetValue è lo stesso codice di indicizzazione da tasto, ad eccezione degli ex restituisce un valore di default (per il parametro out), dove quest'ultimo genera un eccezione. Utilizza TryGetValue e otterrai controlli coerenti senza alcuna perdita di prestazioni.

Modifica: come ha detto Jon, se si sa che avrà sempre la chiave, quindi è possibile indicizzarlo e lasciare che l'eccezione appropriata. Tuttavia, se puoi fornire informazioni contestuali migliori lanciandolo tu stesso con un messaggio dettagliato, sarebbe preferibile.

0

Ci sono 2 treni di pensiero su questo dal punto di vista delle prestazioni.

1) Evitare le eccezioni ove possibile, poiché le eccezioni sono costose, ovvero prima di provare a recuperare una chiave specifica dal dizionario, indipendentemente dal fatto che esista o meno. Un approccio migliore secondo me se c'è una buona possibilità che non esista. Ciò impedirebbe eccezioni abbastanza comuni.

2) Se si è certi che l'articolo esisterà nel 99% del tempo, non verificarne l'esistenza prima di accedervi. L'1% delle volte in cui non esiste, verrà lanciata un'eccezione, ma non hai controllato il tempo per l'altro 99% delle volte.

Quello che sto dicendo è ottimizzare per la maggioranza se ce n'è una chiara. Se c'è un grado reale di incertezza su un oggetto esistente, controllare prima di recuperare.

+0

Non penso che questo dovrebbe davvero riguardare le prestazioni. Dovrebbe essere sulla correttezza: se la chiave è * prevista * per essere lì in situazioni corrette, cioè è un bug per la chiave di non essere lì, quindi lanciare un'eccezione è il giusto approccio al fallimento. Altrimenti, dovresti rilevare quella situazione * senza * usando un'eccezione - la gestione di casi non eccezionali con eccezioni è una cattiva idea, IMO. –

+0

Sto pensando di più dallo scenario in cui non esiste nel dizionario non dovrebbe comportare un fallimento, ad es. dovrebbe usare un valore predefinito, o dovrebbe aggiungerlo se non esiste. Il caso in cui dovrebbe esistere nel dizionario, ma non è un vero bug se non esiste - cioè supponiamo che il dizionario sia una cache di alcuni dati da un DB, diciamo che il DB non è disponibile e la cache non viene popolata, ma non vuoi che ciò causi un guasto nel sistema. È difficile imbattersi in un esempio del mondo reale, quindi spero di avere un certo senso! – AdaTheDev

0

Se si sa che il dizionario normalmente contiene la chiave, non è necessario verificarlo prima di accedervi.

Se qualcosa non va e il dizionario non contiene gli elementi che dovrebbe, è possibile lasciare che il dizionario generi l'eccezione. L'unica ragione per cui si verifica la chiave prima sarebbe se si vuole prendere cura di questa situazione problematica senza ottenere l'eccezione. Lasciare che il dizionario elimini l'eccezione e catturare è comunque un modo perfettamente valido per gestire la situazione.

0

Penso che Marc e Jon ce l'abbiano (come al solito) seminata. Dal momento che menzioni anche le prestazioni nella tua domanda, potrebbe valere la pena considerare come bloccare il dizionario.

Il semplice lock serializza tutti gli accessi in lettura che potrebbero non essere desiderabili se la lettura è molto frequente e le scritture sono relativamente poche. In tal caso, l'utilizzo di ReaderWriterLockSlim potrebbe essere migliore. Il rovescio della medaglia è che il codice è un po 'più complesso e le scritture sono leggermente più lente.

1

Personalmente controllerò la chiave, indipendentemente dal fatto che tu sia sicuro o meno, alcuni potrebbero dire che questo controllo è superfluo e che il dizionario genererà un'eccezione che puoi cogliere, ma non dovresti fidarti su quell'eccezione, dovresti controllare te stesso e poi lanciare la tua eccezione che significa qualcosa o un oggetto risultato con un flag di successo e una ragione dentro ... il meccanismo di errore è davvero dipendente dall'implementazione.

1

Sicuramente la risposta è "tutto dipende dalla situazione".È necessario bilanciare il rischio che la chiave manchi dal dizionario (bassa per i sistemi di piccole dimensioni in cui vi è un accesso limitato ai dati, in cui è possibile fare affidamento sull'ordine in cui vengono eseguite le operazioni, maggiore per i sistemi più grandi, più programmatori che accedono allo stesso dati, specialmente con accesso in lettura/scrittura/eliminazione, dove sono coinvolti i thread e l'ordine non può essere garantito o dove i dati provengono esternamente e la lettura può fallire) con l'impatto del rischio (sistemi critici per la sicurezza, rilasci commerciali o sistemi che un'azienda fare affidamento su rispetto a qualcosa fatto per divertimento, per un lavoro unico e/o solo per uso personale) e con qualsiasi esigenza di velocità, dimensioni e pigrizia.

Se stavo creando un sistema per il controllo della segnaletica ferroviaria, vorrei essere al sicuro da tutti gli errori possibili e impossibili, e al sicuro da errori nella gestione degli errori e così via (Seconda legge di Murphy: "cosa non può andare il torto andrà male "). Se sto buttando le cose insieme per divertimento, anche se le dimensioni e la velocità non sono un problema, sarò MOLTO più rilassato su cose come questa - voglio andare alle cose divertenti.

Naturalmente, a volte questa è la roba divertente di per sé.

Problemi correlati