Cosa è più efficiente: Dizionario TryGetValue o ContainsKey + Item?


252

Dalla voce di MSDN sul metodo Dictionary. TryGetValue :

Questo metodo combina la funzionalità del metodo ContainsKey e la proprietà Item.

Se la chiave non viene trovata, il parametro value ottiene il valore predefinito appropriato per il tipo di valore TValue; ad esempio, 0 (zero) per i tipi interi, false per i tipi booleani e null per i tipi di riferimento.

Utilizzare il metodo TryGetValue se il codice tenta frequentemente di accedere a chiavi che non sono presenti nel dizionario. L'utilizzo di questo metodo è più efficiente rispetto alla rilevazione di KeyNotFoundException generata dalla proprietà Item.

Questo metodo si avvicina a un'operazione O (1).

Dalla descrizione, non è chiaro se sia più efficiente o semplicemente più conveniente che chiamare ContainsKey e quindi eseguire la ricerca. L'implementazione di TryGetValuesolo chiama ContainsKey e quindi Item o è effettivamente più efficiente di quella facendo una singola ricerca?

In altre parole, ciò che è più efficiente (ovvero quale esegue meno ricerche):

Dictionary<int,int> dict;
//...//
int ival;
if(dict.ContainsKey(ikey))
{
  ival = dict[ikey];
}
else
{
  ival = default(int);
}

o

Dictionary<int,int> dict;
//...//
int ival;
dict.TryGetValue(ikey, out ival);

Nota: non sto cercando un punto di riferimento!

Risposte:


314

TryGetValue sarà più veloce.

ContainsKeyutilizza lo stesso controllo di TryGetValue, che si riferisce internamente alla posizione di entrata effettiva. La Itemproprietà in realtà ha funzionalità di codice quasi identiche a TryGetValue, tranne per il fatto che genererà un'eccezione invece di restituire false.

L'utilizzo ContainsKeyseguito da Itemsostanzialmente duplica la funzionalità di ricerca, che è la maggior parte del calcolo in questo caso.


2
Questo è più sottile: if(dict.ContainsKey(ikey)) dict[ikey]++; else dict.Add(ikey, 0);. Ma penso che TryGetValuesia ancora più efficiente poiché vengono utilizzati get e set della proprietà dell'indicizzatore, non è vero?
Tim Schmelter,

4
ora puoi anche cercare l'origine .net: riferimentiource.microsoft.com/#mscorlib/system/collections/… puoi vedere che tutti e 3 i TryGetValue, ContainsKey e questo [] chiamano lo stesso metodo FindEntry e lo fanno la stessa quantità di lavoro, che differisce solo nel modo in cui rispondono alla domanda: trygetvalue restituisce bool e il valore, contiene key restituisce solo true / false e questo [] restituisce il valore o genera un'eccezione.
John Gardner,

1
@JohnGardner Sì, che è quello che ho detto - ma se fai ContainsKey poi ottieni Item, stai facendo quel lavoro 2x invece di 1x.
Reed Copsey,

3
Sono completamente d'accordo :) Stavo solo sottolineando che la fonte effettiva è ora disponibile. nessuna delle altre risposte / ecc. aveva un link alla fonte reale: D
John Gardner,

1
Leggermente fuori tema, se si accede tramite un IDictionary in un ambiente multithread, utilizzerei sempre TryGetValue poiché lo stato potrebbe cambiare dal momento in cui chiami ContainsKey (non vi è alcuna garanzia che TryGetValue si blocchi internamente correttamente, ma probabilmente è più sicuro)
Chris Berry,

91

Un rapido benchmark mostra che TryGetValueha un leggero vantaggio:

    static void Main() {
        var d = new Dictionary<string, string> {{"a", "b"}};
        var start = DateTime.Now;
        for (int i = 0; i != 10000000; i++) {
            string x;
            if (!d.TryGetValue("a", out x)) throw new ApplicationException("Oops");
            if (d.TryGetValue("b", out x)) throw new ApplicationException("Oops");
        }
        Console.WriteLine(DateTime.Now-start);
        start = DateTime.Now;
        for (int i = 0; i != 10000000; i++) {
            string x;
            if (d.ContainsKey("a")) {
                x = d["a"];
            } else {
                x = default(string);
            }
            if (d.ContainsKey("b")) {
                x = d["b"];
            } else {
                x = default(string);
            }
        }
   }

Questo produce

00:00:00.7600000
00:00:01.0610000

rendendo ContainsKey + Itempiù lento l' accesso di circa il 40% ipotizzando una combinazione uniforme di colpi e mancate.

Inoltre, quando cambio programma per perdere sempre (ovvero cercare sempre "b") le due versioni diventano ugualmente veloci:

00:00:00.2850000
00:00:00.2720000

Quando lo faccio "tutti i colpi", tuttavia, TryGetValuerimane un chiaro vincitore:

00:00:00.4930000
00:00:00.8110000

11
Naturalmente, dipende dal modello di utilizzo effettivo. Se non fallisci quasi mai una ricerca, allora TryGetValuedovrebbe essere molto più avanti. Inoltre ... un nitpick ... DateTimenon è il modo migliore per acquisire misurazioni delle prestazioni.
Ed S.

4
@EdS. Hai ragione, TryGetValuediventa ancora più in testa. Ho modificato la risposta per includere uno scenario "tutti i colpi" e "tutti i mancati".
dasblinkenlight,

2
@Luciano spiegare come si è utilizzato Any- Come questo: Any(i=>i.Key==key). In tal caso, sì, è una cattiva ricerca lineare del dizionario.
Weston,

13
DateTime.Nowsarà preciso solo per pochi ms. Utilizzare invece la Stopwatchclasse System.Diagnostics(che utilizza QueryPerformanceCounter sotto le copertine per fornire una precisione molto più elevata). È anche più facile da usare.
Alastair Maw,

5
Oltre ai commenti di Alastair ed Ed - DateTime.Now può tornare indietro, se si ottiene un aggiornamento dell'ora, come quello che si verifica quando l'utente aggiorna l'ora del computer, viene attraversato un fuso orario o il fuso orario cambia (DST, per esempio). Prova a lavorare su un sistema con l'orologio di sistema sincronizzato all'ora fornita da alcuni servizi radio come GPS o reti di telefonia mobile. DateTime.Now andrà dappertutto e DateTime.UtcNow risolve solo una di queste cause. Usa StopWatch.
antiduh,

51

Dato che nessuna delle risposte finora ha effettivamente risposto alla domanda, ecco una risposta accettabile che ho trovato dopo alcune ricerche:

Se decompili TryGetValue vedi che sta facendo questo:

public bool TryGetValue(TKey key, out TValue value)
{
  int index = this.FindEntry(key);
  if (index >= 0)
  {
    value = this.entries[index].value;
    return true;
  }
  value = default(TValue);
  return false;
}

mentre il metodo ContainsKey è:

public bool ContainsKey(TKey key)
{
  return (this.FindEntry(key) >= 0);
}

quindi TryGetValue è solo ContainsKey più una ricerca di array se l'elemento è presente.

fonte

Sembra che TryGetValue sarà quasi due volte più veloce della combinazione ContainsKey + Item.


20

Che importa :-)

Probabilmente lo stai chiedendo perché TryGetValueè un dolore da usare, quindi incapsulalo in questo modo con un metodo di estensione.

public static class CollectionUtils
{
    // my original method
    // public static V GetValueOrDefault<K, V>(this Dictionary<K, V> dic, K key)
    // {
    //    V ret;
    //    bool found = dic.TryGetValue(key, out ret);
    //    if (found)
    //    {
    //        return ret;
    //    }
    //    return default(V);
    // }


    // EDIT: one of many possible improved versions
    public static TValue GetValueOrDefault<K, V>(this IDictionary<K, V> dictionary, K key)
    {
        // initialized to default value (such as 0 or null depending upon type of TValue)
        TValue value;  

        // attempt to get the value of the key from the dictionary
        dictionary.TryGetValue(key, out value);
        return value;
    }

Quindi chiama:

dict.GetValueOrDefault("keyname")

o

(dict.GetValueOrDefault("keyname") ?? fallbackValue) 

1
@ Hüseyin Mi sono confuso molto il modo in cui ero abbastanza stupido da postare questo senza thisma risulta che ho il mio metodo duplicato due volte nella mia base di codice - una con e una senza thisquindi è per questo che non l'ho mai preso! grazie per il fissaggio!
Simon_Weaver,

2
TryGetValueassegna un valore predefinito al parametro out value se la chiave non esiste, quindi questo potrebbe essere semplificato.
Raphael Smit,

2
Versione semplificata: publical TValue GetValueOrDefault <TKey, TValue> (questo dizionario <TKey, TValue> dict, chiave TKey) {TValue ret; dict.TryGetValue (key, out ret); ritorno ret; }
Joshua,

2
In C # 7 è davvero divertente:if(!dic.TryGetValue(key, out value item)) item = dic[key] = new Item();
Shimmy Weitzhandler

1
Ironia della sorte, il vero codice sorgente ha già una routine GetValueOrDefault (), ma è nascosto ... riferimentiource.microsoft.com/#mscorlib/system/collections/…
Deven T. Corzine,

10

Perché non lo provi?

Ma sono abbastanza sicuro che TryGetValuesia più veloce, perché fa solo una ricerca. Naturalmente questo non è garantito, ovvero implementazioni diverse potrebbero avere caratteristiche prestazionali diverse.

Il modo in cui implementerei un dizionario è creando una Findfunzione interna che trova lo slot per un elemento e quindi costruisce il resto.


Non credo che i dettagli dell'implementazione possano cambiare la garanzia che eseguire l'azione X una volta sia più veloce o uguale a fare l'azione X due volte. Nel migliore dei casi sono identici, nel peggiore dei casi la versione 2X richiede il doppio del tempo.
Dan Bechard,

9

Tutte le risposte finora, sebbene buone, mancano di un punto vitale.

I metodi nelle classi di un'API (ad es. Il framework .NET) fanno parte della definizione di un'interfaccia (non un'interfaccia C # o VB, ma un'interfaccia nel significato dell'informatica).

Pertanto, di solito non è corretto chiedere se chiamare tale metodo sia più veloce, a meno che la velocità non faccia parte della definizione formale dell'interfaccia (cosa che non è in questo caso).

Tradizionalmente questo tipo di collegamento (che combina ricerca e recupero) è più efficiente indipendentemente dalla lingua, dall'infrastruttura, dal sistema operativo, dalla piattaforma o dall'architettura della macchina. È anche più leggibile, perché esprime esplicitamente il tuo intento, piuttosto che implicarlo (dalla struttura del tuo codice).

Quindi la risposta (da un vecchio hack grizzled) è sicuramente 'Sì' (TryGetValue è preferibile a una combinazione di ContainsKey e Item [Get] per recuperare un valore da un Dizionario).

Se ritieni che ciò sembri strano, pensalo in questo modo: anche se le attuali implementazioni di TryGetValue, ContainsKey e Item [Get] non producono alcuna differenza di velocità, puoi presumere che sia probabile un'implementazione futura (ad esempio .NET v5) farà (TryGetValue sarà più veloce). Pensa alla durata del tuo software.

Per inciso, è interessante notare che le moderne tecnologie di definizione delle interfacce moderne raramente forniscono ancora qualsiasi mezzo per definire formalmente i vincoli di temporizzazione. Forse .NET v5?


2
Sebbene io sia d'accordo al 100% con le tue argomentazioni sulla semantica, vale comunque la pena fare il test delle prestazioni. Non si sa mai quando l'API che si sta utilizzando ha un'implementazione non ottimale in modo che la cosa semanticamente corretta sia più lenta, a meno che non si esegua il test.
Dan Bechard,

5

Effettuando un programma di test rapido, c'è sicuramente un miglioramento usando TryGetValue con 1 milione di voci in un dizionario.

risultati:

Contiene Chiave + oggetto per 1000000 colpi: 45ms

TryGetValue per 1000000 hit: 26ms

Ecco l'app di test:

static void Main(string[] args)
{
    const int size = 1000000;

    var dict = new Dictionary<int, string>();

    for (int i = 0; i < size; i++)
    {
        dict.Add(i, i.ToString());
    }

    var sw = new Stopwatch();
    string result;

    sw.Start();

    for (int i = 0; i < size; i++)
    {
        if (dict.ContainsKey(i))
            result = dict[i];
    }

    sw.Stop();
    Console.WriteLine("ContainsKey + Item for {0} hits: {1}ms", size, sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();

    for (int i = 0; i < size; i++)
    {
        dict.TryGetValue(i, out result);
    }

    sw.Stop();
    Console.WriteLine("TryGetValue for {0} hits: {1}ms", size, sw.ElapsedMilliseconds);

}

5

Sulla mia macchina, con un sacco di RAM, quando eseguito in modalità RELEASE (non DEBUG), è ContainsKeyuguale TryGetValue/ try-catchse Dictionary<>vengono trovate tutte le voci in .

ContainsKeyli supera di gran lunga tutti quando ci sono solo alcune voci del dizionario non trovate (nel mio esempio di seguito, impostato MAXVALsu qualcosa di più grande di quello ENTRIESdi perdere alcune voci):

risultati:

Finished evaluation .... Time distribution:
Size: 000010: TryGetValue: 53,24%, ContainsKey: 1,74%, try-catch: 45,01% - Total: 2.006,00
Size: 000020: TryGetValue: 37,66%, ContainsKey: 0,53%, try-catch: 61,81% - Total: 2.443,00
Size: 000040: TryGetValue: 22,02%, ContainsKey: 0,73%, try-catch: 77,25% - Total: 7.147,00
Size: 000080: TryGetValue: 31,46%, ContainsKey: 0,42%, try-catch: 68,12% - Total: 17.793,00
Size: 000160: TryGetValue: 33,66%, ContainsKey: 0,37%, try-catch: 65,97% - Total: 36.840,00
Size: 000320: TryGetValue: 34,53%, ContainsKey: 0,39%, try-catch: 65,09% - Total: 71.059,00
Size: 000640: TryGetValue: 32,91%, ContainsKey: 0,32%, try-catch: 66,77% - Total: 141.789,00
Size: 001280: TryGetValue: 39,02%, ContainsKey: 0,35%, try-catch: 60,64% - Total: 244.657,00
Size: 002560: TryGetValue: 35,48%, ContainsKey: 0,19%, try-catch: 64,33% - Total: 420.121,00
Size: 005120: TryGetValue: 43,41%, ContainsKey: 0,24%, try-catch: 56,34% - Total: 625.969,00
Size: 010240: TryGetValue: 29,64%, ContainsKey: 0,61%, try-catch: 69,75% - Total: 1.197.242,00
Size: 020480: TryGetValue: 35,14%, ContainsKey: 0,53%, try-catch: 64,33% - Total: 2.405.821,00
Size: 040960: TryGetValue: 37,28%, ContainsKey: 0,24%, try-catch: 62,48% - Total: 4.200.839,00
Size: 081920: TryGetValue: 29,68%, ContainsKey: 0,54%, try-catch: 69,77% - Total: 8.980.230,00

Ecco il mio codice:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;

    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                const int ENTRIES = 10000, MAXVAL = 15000, TRIALS = 100000, MULTIPLIER = 2;
                Dictionary<int, int> values = new Dictionary<int, int>();
                Random r = new Random();
                int[] lookups = new int[TRIALS];
                int val;
                List<Tuple<long, long, long>> durations = new List<Tuple<long, long, long>>(8);

                for (int i = 0;i < ENTRIES;++i) try
                    {
                        values.Add(r.Next(MAXVAL), r.Next());
                    }
                    catch { --i; }

                for (int i = 0;i < TRIALS;++i) lookups[i] = r.Next(MAXVAL);

                Stopwatch sw = new Stopwatch();
                ConsoleColor bu = Console.ForegroundColor;

                for (int size = 10;size <= TRIALS;size *= MULTIPLIER)
                {
                    long a, b, c;

                    Console.ForegroundColor = ConsoleColor.Yellow;
                    Console.WriteLine("Loop size: {0}", size);
                    Console.ForegroundColor = bu;

                    // ---------------------------------------------------------------------
                    sw.Start();
                    for (int i = 0;i < size;++i) values.TryGetValue(lookups[i], out val);
                    sw.Stop();
                    Console.WriteLine("TryGetValue: {0}", a = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    sw.Restart();
                    for (int i = 0;i < size;++i) val = values.ContainsKey(lookups[i]) ? values[lookups[i]] : default(int);
                    sw.Stop();
                    Console.WriteLine("ContainsKey: {0}", b = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    sw.Restart();
                    for (int i = 0;i < size;++i)
                        try { val = values[lookups[i]]; }
                        catch { }
                    sw.Stop();
                    Console.WriteLine("try-catch: {0}", c = sw.ElapsedTicks);

                    // ---------------------------------------------------------------------
                    Console.WriteLine();

                    durations.Add(new Tuple<long, long, long>(a, b, c));
                }

                Console.ForegroundColor = ConsoleColor.Yellow;
                Console.WriteLine("Finished evaluation .... Time distribution:");
                Console.ForegroundColor = bu;

                val = 10;
                foreach (Tuple<long, long, long> d in durations)
                {
                    long sum = d.Item1 + d.Item2 + d.Item3;

                    Console.WriteLine("Size: {0:D6}:", val);
                    Console.WriteLine("TryGetValue: {0:P2}, ContainsKey: {1:P2}, try-catch: {2:P2} - Total: {3:N}", (decimal)d.Item1 / sum, (decimal)d.Item2 / sum, (decimal)d.Item3 / sum, sum);
                    val *= MULTIPLIER;
                }

                Console.WriteLine();
            }
        }
    }

Sento che sta succedendo qualcosa di sospetto qui. Mi chiedo se l'ottimizzatore potrebbe rimuovere o semplificare i controlli ContainsKey () a causa del fatto che non si utilizza mai il valore recuperato.
Dan Bechard,

Non può proprio. ContainsKey () si trova in una DLL compilata. L'ottimizzatore non sa nulla di ciò che effettivamente ContainsKey (). Potrebbe causare effetti collaterali, quindi deve essere chiamato e non può essere abbreviato.
AxD

Qui c'è qualcosa di falso. Il fatto è che l'esame del codice .NET mostra che ContainsKey, TryGetValue e questo [] tutti chiamano lo stesso codice interno, quindi TryGetValue è più veloce di ContainsKey + this [] quando esiste la voce.
Jim Balter,

3

Oltre a progettare un microbenchmark che fornirà risultati accurati in un'impostazione pratica, è possibile controllare l'origine di riferimento di .NET Framework.

Tutti chiamano il FindEntry(TKey)metodo che svolge la maggior parte del lavoro e non memorizza il risultato, quindi la chiamata TryGetValueè quasi doppia rispetto a ContainsKey+Item .


L'interfaccia scomoda di TryGetValuepuò essere adattata utilizzando un metodo di estensione :

using System.Collections.Generic;

namespace Project.Common.Extensions
{
    public static class DictionaryExtensions
    {
        public static TValue GetValueOrDefault<TKey, TValue>(
            this IDictionary<TKey, TValue> dictionary,
            TKey key,
            TValue defaultValue = default(TValue))
        {
            if (dictionary.TryGetValue(key, out TValue value))
            {
                return value;
            }
            return defaultValue;
        }
    }
}

Da C # 7.1, è possibile sostituire default(TValue)con plain default. Il tipo è inferito.

Uso:

var dict = new Dictionary<string, string>();
string val = dict.GetValueOrDefault("theKey", "value used if theKey is not found in dict");

Restituisce nullper i tipi di riferimento la cui ricerca non riesce, a meno che non venga specificato un valore predefinito esplicito.

var dictObj = new Dictionary<string, object>();
object valObj = dictObj.GetValueOrDefault("nonexistent");
Debug.Assert(valObj == null);

val dictInt = new Dictionary<string, int>();
int valInt = dictInt.GetValueOrDefault("nonexistent");
Debug.Assert(valInt == 0);

Si noti che gli utenti del metodo di estensione non possono dire la differenza tra una chiave inesistente e una chiave esistente ma il suo valore è predefinito (T).
Lucas,

Su un computer moderno, se si chiama una subroutine due volte in rapida successione, è improbabile che impieghi il doppio della chiamata una volta. Questo perché è molto probabile che l'architettura della CPU e della cache memorizzi nella cache molte istruzioni e dati associati alla prima chiamata, quindi la seconda chiamata verrà eseguita più velocemente. D'altra parte, chiamare due volte è quasi certo che impiegherà un po 'più di tempo rispetto a chiamare una volta, quindi c'è ancora un vantaggio nell'eliminare la seconda chiamata, se possibile.
dibattente il

2

Se stai cercando di estrarre il valore dal dizionario, TryGetValue (chiave, valore out) è l'opzione migliore, ma se stai verificando la presenza della chiave, per un nuovo inserimento, senza sovrascrivere le vecchie chiavi, e solo con tale ambito, ContainsKey (chiave) è l'opzione migliore, il benchmark può confermarlo:

using System;
using System.Threading;
using System.Diagnostics;
using System.Collections.Generic;
using System.Collections;

namespace benchmark
{
class Program
{
    public static Random m_Rand = new Random();
    public static Dictionary<int, int> testdict = new Dictionary<int, int>();
    public static Hashtable testhash = new Hashtable();

    public static void Main(string[] args)
    {
        Console.WriteLine("Adding elements into hashtable...");
        Stopwatch watch = Stopwatch.StartNew();
        for(int i=0; i<1000000; i++)
            testhash[i]=m_Rand.Next();
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- pause....", watch.Elapsed.TotalSeconds);
        Thread.Sleep(4000);
        Console.WriteLine("Adding elements into dictionary...");
        watch = Stopwatch.StartNew();
        for(int i=0; i<1000000; i++)
            testdict[i]=m_Rand.Next();
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- pause....", watch.Elapsed.TotalSeconds);
        Thread.Sleep(4000);

        Console.WriteLine("Finding the first free number for insertion");
        Console.WriteLine("First method: ContainsKey");
        watch = Stopwatch.StartNew();
        int intero=0;
        while (testdict.ContainsKey(intero))
        {
            intero++;
        }
        testdict.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} in dictionary -- pause....", watch.Elapsed.TotalSeconds, intero);
        Thread.Sleep(4000);
        Console.WriteLine("Second method: TryGetValue");
        watch = Stopwatch.StartNew();
        intero=0;
        int result=0;
        while(testdict.TryGetValue(intero, out result))
        {
            intero++;
        }
        testdict.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} in dictionary -- pause....", watch.Elapsed.TotalSeconds, intero);
        Thread.Sleep(4000);
        Console.WriteLine("Test hashtable");
        watch = Stopwatch.StartNew();
        intero=0;
        while(testhash.Contains(intero))
        {
            intero++;
        }
        testhash.Add(intero, m_Rand.Next());
        watch.Stop();
        Console.WriteLine("Done in {0:F4} -- added value {1} into hashtable -- pause....", watch.Elapsed.TotalSeconds, intero);
        Console.Write("Press any key to continue . . . ");
        Console.ReadKey(true);
    }
}
}

Questo è un vero esempio, ho un servizio che per ogni "Articolo" creato, associa un numero progressivo, questo numero, ogni volta che crei un nuovo oggetto, deve essere trovato gratuitamente, se elimini un Articolo, il numero libero diventa gratis, ovviamente questo non è ottimizzato, dato che ho una var statica che memorizza nella cache il numero corrente, ma nel caso in cui finisci tutti i numeri, puoi ricominciare da 0 a UInt32.MaxValue

Test eseguito:
aggiunta di elementi nell'hashtable ...
Fatto in 0,5908 - pausa ....
Aggiunta di elementi nel dizionario ...
Fatto in 0,2679 - pausa ....
Ricerca del primo numero libero per l'inserimento
Primo metodo : ContieneKey
Fatto in 0,0561 - valore aggiunto 1000000 nel dizionario - pausa ....
Secondo metodo: TryGetValue
Fatto in 0,0643 - valore aggiunto 1000001 nel dizionario - pausa ....
Test hashtable
Fatto in 0, 3015 - valore aggiunto 1000000 in hashtable - pausa ....
Premere un tasto qualsiasi per continuare. .

Se alcuni di voi potrebbero chiedere se ContainsKeys potrebbe avere un vantaggio, ho anche provato a invertire la chiave TryGetValue con Contains, il risultato è lo stesso.

Quindi, per me, con una considerazione finale, tutto dipende dal modo in cui il programma si comporta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.