C# Dictionary<TKey, TValue>

Numa List, você encontra as coisas pela posição: o item 0, o item 1, o item 2. Mas muitas vezes você quer encontrar pelo nome: o preço do café, a nota da Ana, o estoque de bananas. Para isso existe o dicionário: Dictionary<TKey, TValue>. Ele liga cada chave a um valor e acha qualquer chave num piscar de olhos, mesmo com um milhão de itens.

Você vai aprender
  • O que são chave e valor, e como criar um Dictionary.
  • Guardar e ler com o indexador [ ], e a diferença para o Add.
  • O que acontece quando a chave não existe: a KeyNotFoundException.
  • Perguntar antes: ContainsKey, TryGetValue, GetValueOrDefault, TryAdd e Remove.
  • Percorrer com foreach, KeyValuePair, Keys e Values.
  • Contar quantas vezes cada palavra aparece.
  • Por que você não deve contar com a ordem e quando usar o SortedDictionary.
  • A InvalidOperationException de mudar o dicionário dentro do foreach.

Todos os programas desta aula rodaram com o .NET 10 na máquina da Ana (usuária ana, computador notebook), na pasta ~/cs, com dotnet run arquivo.cs (veja Rodar com dotnet run). Você pode repetir cada um no editor do guia.


A ideia: um dicionário de papel

Num dicionário de papel, você não procura "a palavra número 3.512": você procura pela palavra e encontra o significado. A palavra é a chave (key); o significado é o valor (value). A tabela de preços da lojinha da Ana é igual:

  chave        valor
  "pão"    →   0,75
  "leite"  →   5,49
  "café"   →  18,90

Duas regras definem um dicionário:

  • Cada chave aparece uma vez só. Não existem dois "pão". Guardar "pão" de novo troca o valor que estava lá.
  • Os valores podem se repetir. O pão e a banana podem custar o mesmo.

No nome Dictionary<TKey, TValue>, os dois tipos entre < > são escolhidos por você: TKey é o tipo da chave e TValue, o do valor. Um Dictionary<string, decimal> é "um dicionário de texto para dinheiro". Essa escolha de tipos entre < > se chama generics e tem aula própria (Generics). O Dictionary mora no namespace System.Collections.Generic, que o .NET já importa sozinho (o using implícito; veja Namespaces e using).


1. Criar, guardar e ler: o indexador [ ]

var precos = new Dictionary<string, decimal>();

precos["pão"] = 0.75m;
precos["leite"] = 5.49m;
precos["café"] = 18.90m;

Console.WriteLine($"Produtos: {precos.Count}");
Console.WriteLine($"Leite: {precos["leite"]:C}");

precos["leite"] = 5.99m;
Console.WriteLine($"Leite agora: {precos["leite"]:C}");
Console.WriteLine($"Produtos: {precos.Count}");

Console.WriteLine(precos);
new Dictionary<string, decimal>()
Cria um dicionário vazio. A chave é string (o nome do produto) e o valor é decimal (o preço; dinheiro é sempre decimal, veja float, double e decimal).
precos["pão"] = 0.75m;
O indexador: os colchetes com a chave dentro, em vez de um número de posição. Leia "o preço do pão recebe 0,75". Se a chave não existe, ela é criada; se já existe, o valor é trocado.
precos.Count
Count = contagem. Quantos pares chave-valor existem.
precos["leite"]
Sem o = depois, o indexador lê: devolve o valor ligado à chave "leite".
:C
Formata como moeda (R$), como em Formatar Números.
$ dotnet run precos.cs
Produtos: 3
Leite: R$ 5,49
Leite agora: R$ 5,99                                       « a mesma chave: o valor foi trocado
Produtos: 3                                                « continua com 3, não criou outro leite
System.Collections.Generic.Dictionary`2[System.String,System.Decimal]

Repare na última linha. Diferente de outras linguagens, o C# não mostra o conteúdo do dicionário quando você o passa para o Console.WriteLine: ele mostra só o nome do tipo (o `2 quer dizer "com 2 tipos entre < >"). Para ver o que tem dentro, você percorre o dicionário com foreach, como na seção 4.

O Add: guardar só se a chave for nova

Também dá para guardar com o método Add ("acrescentar"). A diferença aparece quando a chave já existe: o indexador troca o valor em silêncio, mas o Add recusa e lança uma exceção.

var precos = new Dictionary<string, decimal>();

precos.Add("pão", 0.75m);
precos.Add("leite", 5.49m);
Console.WriteLine($"Produtos: {precos.Count}");

precos.Add("pão", 0.80m);
Console.WriteLine("Esta linha não roda.");
$ dotnet run add.cs
Produtos: 2
Unhandled exception. System.ArgumentException: An item with the same key has already been added. Key: pão
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Program.<Main>$(String[] args) in /home/ana/cs/add.cs:line 7
$ echo $?
134

A mensagem, em português: "um item com a mesma chave já foi adicionado. Chave: pão". A última linha do rastro diz onde: add.cs:line 7. Isso não é um defeito do Add, é o trabalho dele: quando uma chave repetida é um erro nos seus dados (dois alunos com a mesma matrícula, por exemplo), o Add avisa na hora em vez de apagar o primeiro sem você perceber.

EscritaChave novaChave que já existe
d[chave] = valor;Cria o parTroca o valor (sem aviso)
d.Add(chave, valor);Cria o parLança ArgumentException
d.TryAdd(chave, valor)Cria o par e devolve trueNão mexe e devolve false (seção 3)

Criar já com itens

Em vez de uma linha por item, você pode encher o dicionário na criação, com um inicializador entre chaves. Há duas formas, e as duas dão o mesmo dicionário:

var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 30,
};

var notas = new Dictionary<string, double>
{
    { "Ana", 9.5 },
    { "Bruno", 7.0 },
};
["maçã"] = 12,
Usa o indexador para cada item. Se a mesma chave aparecer duas vezes, vale a última.
{ "Ana", 9.5 },
Usa o Add para cada item. Uma chave repetida dá a ArgumentException que você acabou de ver.
a vírgula depois do último item
É permitida e comum: facilita acrescentar uma linha depois.
E os colchetes [ ] das listas?

Uma lista pode ser criada com a expressão de coleção List<int> n = [1, 2, 3]; C# 12. Para dicionários isso ainda não existe no C# 14: Dictionary<string, int> d = ["a": 1]; dá error CS1003: Erro de sintaxe, "]" esperado. Use um dos dois inicializadores acima.

Missão: o café subiu. Logo depois do Console.WriteLine do preço antigo, troque o preço do café para 19.50m usando o indexador, e mostre a linha Café: R$ 19,50. O número de produtos tem de continuar 3.

var precos = new Dictionary<string, decimal>
{
    ["pão"] = 0.75m,
    ["leite"] = 5.49m,
    ["café"] = 18.90m,
};

Console.WriteLine($"Café: {precos["café"]:C}");

// troque o preço do café aqui e mostre de novo

Console.WriteLine($"Produtos: {precos.Count}");
var precos = new Dictionary<string, decimal>
{
    ["pão"] = 0.75m,
    ["leite"] = 5.49m,
    ["café"] = 18.90m,
};

Console.WriteLine($"Café: {precos["café"]:C}");

precos["café"] = 19.50m;
Console.WriteLine($"Café: {precos["café"]:C}");

Console.WriteLine($"Produtos: {precos.Count}");

2. A chave que não existe: KeyNotFoundException

Ler com o indexador é direto, mas tem um preço: se a chave não existe, o programa para com uma exceção.

var precos = new Dictionary<string, decimal>();
precos["pão"] = 0.75m;
precos["leite"] = 5.49m;

Console.WriteLine($"Queijo: {precos["queijo"]:C}");
$ dotnet run queijo.cs
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key 'queijo' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at Program.<Main>$(String[] args) in /home/ana/cs/queijo.cs:line 5
$ echo $?
134
KeyNotFoundException
"Exceção de chave não encontrada". O nome já diz tudo.
The given key 'queijo' was not present in the dictionary.
"A chave 'queijo' não estava presente no dicionário." O .NET mostra qual chave você pediu: confira se ela foi guardada e se está escrita igual (maiúsculas, acentos, espaços).
get_Item(TKey key)
É o nome interno do indexador quando ele lê (get = pegar).
queijo.cs:line 5
A linha do seu programa que pediu a chave.

Como toda exceção não tratada, ela termina o programa com código de saída 134 (veja Exceções). Você poderia cercar a leitura com try e catch, mas existe um jeito melhor e mais barato: perguntar antes.


3. Perguntar antes: ContainsKey, TryGetValue e companhia

var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 30,
    ["uva"] = 0,
};

Console.WriteLine(estoque.ContainsKey("uva"));
Console.WriteLine(estoque.ContainsKey("kiwi"));

if (estoque.TryGetValue("banana", out int bananas))
    Console.WriteLine($"Tem {bananas} bananas.");

if (!estoque.TryGetValue("kiwi", out int kiwis))
    Console.WriteLine($"Não tem kiwi (kiwis = {kiwis}).");

Console.WriteLine(estoque.GetValueOrDefault("kiwi"));
Console.WriteLine(estoque.GetValueOrDefault("kiwi", -1));

Console.WriteLine(estoque.TryAdd("maçã", 99));
Console.WriteLine(estoque.TryAdd("pera", 7));

Console.WriteLine(estoque.Remove("uva"));
Console.WriteLine(estoque.Remove("uva"));
Console.WriteLine($"Itens: {estoque.Count}");
$ dotnet run perguntar.cs
True                              « a uva existe, mesmo com estoque 0
False
Tem 30 bananas.
Não tem kiwi (kiwis = 0).         « quando falha, o out recebe o valor padrão (0 para int)
0
-1                                « o padrão que você escolheu
False                             « TryAdd: a maçã já existia, continua 12
True                              « a pera entrou com 7
True                              « Remove: tirou a uva
False                             « a uva já não estava lá: nada a tirar, e sem exceção
Itens: 3
LinhaTraduçãoO que faz
ContainsKey(k)"contém a chave"true se a chave existe. Muito rápido.
TryGetValue(k, out int v)"tentar pegar o valor"Se a chave existe, põe o valor em v e devolve true. Se não existe, põe o valor padrão do tipo (0, null, false) e devolve false. Nunca lança exceção. O out está em ref, out e in.
GetValueOrDefault(k)"pegar o valor ou o padrão"O valor da chave, ou o padrão do tipo se ela não existe.
GetValueOrDefault(k, p)O mesmo, mas quem escolhe o padrão é você (p).
TryAdd(k, v)"tentar acrescentar"Só guarda se a chave é nova. Devolve true se guardou.
Remove(k)"remover"Tira o par. Devolve true se a chave existia e false se não existia.
ContainsValue(v)"contém o valor"true se algum par tem esse valor. Lento: olha todos os pares, um por um.
Clear()"limpar"Tira todos os pares.
ContainsKey + indexador, ou TryGetValue?

Este código funciona, mas procura a chave duas vezes:

if (estoque.ContainsKey("banana"))
    Console.WriteLine(estoque["banana"]);

O TryGetValue pergunta e pega numa busca só. Quando você vai usar o valor, prefira ele. O ContainsKey fica para quando você só quer saber "existe ou não".

Missão: rode e veja a KeyNotFoundException do kiwi. Depois troque as duas leituras com [ ] por GetValueOrDefault (ou por TryGetValue), para a saída ser maçã: 12 e kiwi: 0, sem exceção.

var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 30,
};

Console.WriteLine($"maçã: {estoque["maçã"]}");
Console.WriteLine($"kiwi: {estoque["kiwi"]}");
var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 30,
};

Console.WriteLine($"maçã: {estoque.GetValueOrDefault("maçã")}");
Console.WriteLine($"kiwi: {estoque.GetValueOrDefault("kiwi")}");

4. Percorrer: foreach, KeyValuePair, Keys e Values

O foreach (aula foreach) passa por todos os pares do dicionário. Cada par é um KeyValuePair<TKey, TValue> ("par chave-valor"), com duas propriedades: Key (a chave) e Value (o valor).

var notas = new Dictionary<string, double>
{
    ["Ana"] = 9.5,
    ["Bruno"] = 7.0,
    ["Carla"] = 8.25,
};

foreach (KeyValuePair<string, double> par in notas)
{
    Console.WriteLine($"{par.Key} tirou {par.Value}");
}

foreach (var (nome, nota) in notas)
{
    Console.WriteLine($"{nome}: {nota}");
}

Console.WriteLine(string.Join(", ", notas.Keys));
Console.WriteLine(notas.Values.Sum() / notas.Count);

foreach (var par in notas)
{
    Console.WriteLine(par);
}
$ dotnet run percorrer.cs
Ana tirou 9,5
Bruno tirou 7
Carla tirou 8,25
Ana: 9,5
Bruno: 7
Carla: 8,25
Ana, Bruno, Carla
8,25                     « a média: (9,5 + 7 + 8,25) / 3
[Ana, 9,5]               « um KeyValuePair se mostra assim: [chave, valor]
[Bruno, 7]
[Carla, 8,25]
foreach (KeyValuePair<string, double> par in notas)
"Para cada par em notas". O tipo completo é comprido; na prática quase todo mundo escreve var par.
par.Key / par.Value
A chave e o valor do par atual.
foreach (var (nome, nota) in notas)
A forma moderna: o par é desmontado em duas variáveis com nomes que você escolhe (a desconstrução, a mesma das Tuplas). Fica mais fácil de ler que par.Key.
notas.Keys
Só as chaves. Aqui o string.Join as junta com ", ".
notas.Values.Sum()
Só os valores, somados pelo Sum do LINQ.
Console.WriteLine(par)
Mostra o par entre colchetes. Cuidado com a cultura pt-BR: em [Ana, 9,5], a primeira vírgula separa chave e valor, e a segunda é a vírgula decimal do 9,5.

Quando precisar da chave e do valor, percorra os pares (como nos dois primeiros laços). Fazer foreach (var nome in notas.Keys) e ler notas[nome] lá dentro funciona, mas busca cada chave de novo, sem necessidade.


5. Contar coisas: a frequência das palavras

O uso mais clássico de um dicionário: contar quantas vezes cada coisa aparece. A chave é a coisa (a palavra); o valor é quantas vezes ela apareceu.

string frase = "o rato roeu a roupa do rei de roma e o rato fugiu";
var contagem = new Dictionary<string, int>();

foreach (string palavra in frase.Split(' '))
{
    if (contagem.TryGetValue(palavra, out int vezes))
        contagem[palavra] = vezes + 1;
    else
        contagem[palavra] = 1;
}

Console.WriteLine($"Palavras diferentes: {contagem.Count}");
Console.WriteLine($"rato: {contagem["rato"]}");
Console.WriteLine($"o: {contagem["o"]}");
Console.WriteLine($"rei: {contagem["rei"]}");
$ dotnet run contar.cs
Palavras diferentes: 11
rato: 2
o: 2
rei: 1
frase.Split(' ')
Corta a frase em cada espaço e devolve um array com as 13 palavras (veja Métodos de String).
if (contagem.TryGetValue(palavra, out int vezes))
"Essa palavra já foi contada? Se sim, quantas vezes?"
contagem[palavra] = vezes + 1;
Já existia: guarda a contagem antiga mais 1.
contagem[palavra] = 1;
É a primeira vez: começa com 1.

São 13 palavras, mas só 11 diferentes, porque "o" e "rato" aparecem duas vezes. O mesmo laço fica numa linha só com o GetValueOrDefault: "pegue a contagem atual, ou 0 se é a primeira vez, e some 1":

foreach (string palavra in frase.Split(' '))
    contagem[palavra] = contagem.GetValueOrDefault(palavra) + 1;

6. A ordem não é garantida

Por dentro, o Dictionary não guarda os pares numa fila. Ele calcula um número a partir de cada chave (o hash code, uma "impressão digital" numérica; veja A Classe object) e usa esse número para achar a chave depois sem olhar as outras. É por isso que ele é tão rápido.

Num dicionário em que você só acrescenta, o foreach do .NET costuma devolver os pares na ordem em que entraram, como você viu nas notas. Mas isso é um detalhe de como ele foi feito, não uma promessa. Basta uma remoção para a ordem mudar:

var frutas = new Dictionary<string, int>();
frutas["uva"] = 1;
frutas["banana"] = 2;
frutas["maçã"] = 3;
frutas["kiwi"] = 4;
Console.WriteLine(string.Join(", ", frutas.Keys));

frutas.Remove("banana");
frutas["pera"] = 5;
Console.WriteLine(string.Join(", ", frutas.Keys));

var ordenado = new SortedDictionary<string, int>(frutas);
Console.WriteLine(string.Join(", ", ordenado.Keys));
$ dotnet run ordem.cs
uva, banana, maçã, kiwi
uva, pera, maçã, kiwi          « a pera entrou por último, mas ocupou o lugar vago da banana
kiwi, maçã, pera, uva          « SortedDictionary: chaves em ordem alfabética

A pera foi a última a entrar, mas apareceu em segundo: ela reaproveitou o espaço que a banana deixou. Se o seu programa só "funciona" porque o dicionário por acaso mostrou na ordem certa, ele pode quebrar com a primeira remoção.

Nunca dependa da ordem de um Dictionary

Quando a ordem importa, diga isso no código:

  • Ordem alfabética (ou numérica) das chaves: SortedDictionary<TKey, TValue>, que mantém as chaves sempre ordenadas.
  • Uma ordem só na hora de mostrar: ordene as chaves antes, com o Order() do LINQ: foreach (var k in frutas.Keys.Order()).
  • A ordem em que os itens chegaram: guarde-os também numa List.

7. Mudar o dicionário dentro do foreach

Enquanto o foreach anda pelo dicionário, você não pode acrescentar chaves nele. A Ana quis marcar as frutas que acabaram criando uma chave nova "… (encomendar)" no meio do laço:

var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 0,
    ["uva"] = 5,
};

foreach (var (fruta, qtd) in estoque)
{
    if (qtd == 0)
        estoque.Add($"{fruta} (encomendar)", 50);
}
$ dotnet run modificar.cs
Unhandled exception. System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
   at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   at Program.<Main>$(String[] args) in /home/ana/cs/modificar.cs:line 8
$ echo $?
134

Em português: "a coleção foi modificada; a operação de enumeração (o percurso) não pode continuar". O Add em si funcionou; quem reclamou foi o foreach, quando tentou ir para o próximo par (MoveNext, "mover para o próximo"). Por isso o rastro aponta a linha 8, a do foreach, e não a do Add. Acrescentar uma chave pode reorganizar o dicionário por dentro, e o percurso perderia o fio.

O conserto é fazer em dois passos: primeiro anote numa lista o que precisa mudar; depois, fora do laço, mude.

var estoque = new Dictionary<string, int>
{
    ["maçã"] = 12,
    ["banana"] = 0,
    ["uva"] = 5,
    ["kiwi"] = 0,
};

var acabaram = new List<string>();
foreach (var (fruta, qtd) in estoque)
{
    if (qtd == 0)
        acabaram.Add(fruta);
}

foreach (string fruta in acabaram)
{
    estoque.Remove(fruta);
    estoque[$"{fruta} (encomendar)"] = 50;
}

Console.WriteLine(string.Join(", ", estoque.Keys.Order()));
$ dotnet run modificar2.cs
banana (encomendar), kiwi (encomendar), maçã, uva

O segundo foreach anda pela lista acabaram, não pelo dicionário, então mexer no dicionário ali é seguro. Como houve remoções, o programa ordena as chaves com Order() antes de mostrar (lembre da seção 6).

E remover dentro do foreach?

Desde o .NET Core 3.0, o Dictionary deixa você remover a chave atual ou trocar o valor de uma chave que já existe durante o foreach, sem exceção. Só acrescentar chaves é proibido. Mesmo assim, o jeito em dois passos funciona em qualquer coleção (a List, por exemplo, reclama também de remoções) e deixa claro o que o código faz.


8. Maiúsculas e minúsculas na chave

Para o dicionário, "Ana" e "ana" são chaves diferentes. Se você quer que ele ignore a caixa das letras, passe um comparador na criação:

var senhas = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
senhas["Ana"] = "girassol";

Console.WriteLine(senhas.ContainsKey("ana"));
Console.WriteLine(senhas["ANA"]);

var normal = new Dictionary<string, string>();
normal["Ana"] = "girassol";
Console.WriteLine(normal.ContainsKey("ana"));
$ dotnet run caixa.cs
True
girassol
False                    « sem o comparador, "ana" não é "Ana"

StringComparer.OrdinalIgnoreCase quer dizer "comparar letra por letra, ignorando maiúsculas e minúsculas". É útil para nomes de usuário, comandos digitados e extensões de arquivo.


Qual coleção usar?

Você quer…Use
Itens em fila, acessados pela posição (0, 1, 2…)List<T>
Achar um valor pela chave, rápido; a ordem não importaDictionary<TKey, TValue>
Achar pela chave e percorrer em ordem de chaveSortedDictionary<TKey, TValue>
Só saber se um item está ou não está, sem valor ligadoHashSet<T>

Quando dá errado

KeyNotFoundException: The given key 'queijo' was not present in the dictionary.

O que significa: você leu d[chave] com uma chave que não está no dicionário. O programa para com código 134.

Como consertar: confira a grafia da chave (a mensagem mostra exatamente o que foi pedido). Se a chave pode faltar, leia com TryGetValue ou GetValueOrDefault, como na seção 3.

ArgumentException: An item with the same key has already been added. Key: pão

O que significa: um Add (ou um inicializador { "pão", … }) tentou guardar uma chave que já existe.

Como consertar: se trocar o valor é o que você quer, use o indexador: d["pão"] = 0.80m;. Se a chave nova não deve apagar a antiga, use TryAdd e veja o bool que ele devolve. Se a repetição é um erro nos dados, a exceção está certa: conserte os dados.

InvalidOperationException: Collection was modified; enumeration operation may not execute.

O que significa: você acrescentou uma chave enquanto um foreach percorria o mesmo dicionário. O rastro aponta a linha do foreach.

Como consertar: faça em dois passos: anote numa List o que mudar e mude depois do laço (seção 7).

error CS1503: Argumento 1: não é possível converter de "int" para "string"

O que significa: a chave que você passou não é do tipo TKey. Com Dictionary<string, int> estoque, a linha estoque[1] tenta usar um número como chave, como se fosse uma lista:

$ dotnet run tipo.cs
/home/ana/cs/tipo.cs(2,27): error CS1503: Argumento 1: não é possível converter de "int" para "string"

Ocorreu uma falha no build. Corrija os erros de build e execute novamente.

Como consertar: um dicionário não tem posições, só chaves. Use a chave (estoque["maçã"]). Se você precisa "do primeiro item", talvez a coleção certa seja uma List.


Referência rápida

EscritaO que faz
new Dictionary<TKey, TValue>()Cria um dicionário vazio
new Dictionary<string, int> { ["a"] = 1 }Cria já com itens (indexador)
new Dictionary<string, int> { { "a", 1 } }Cria já com itens (Add)
d[k] = v;Cria ou troca o valor de k
d[k]Lê o valor de k; KeyNotFoundException se não existe
d.Add(k, v)Cria; ArgumentException se k já existe
d.TryAdd(k, v)Cria só se k é nova; devolve bool
d.TryGetValue(k, out var v)Busca sem exceção; devolve bool
d.GetValueOrDefault(k[, padrão])O valor ou um padrão
d.ContainsKey(k) / d.ContainsValue(v)Existe a chave? (rápido) / Existe o valor? (lento)
d.Remove(k)Tira o par; devolve bool
d.Count / d.Clear()Quantos pares / tira todos
d.Keys / d.ValuesSó as chaves / só os valores
foreach (var (k, v) in d)Percorre os pares, já desmontados
new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase)Chaves de texto sem diferenciar maiúsculas
SortedDictionary<TKey, TValue>Mantém as chaves em ordem

Exercícios

1. Ler sem exceção

Complete para ler o estoque de "kiwi" sem risco de KeyNotFoundException:

if (estoque.("kiwi", out int qtd))

2. Os tipos do dicionário

Complete a criação de um dicionário que liga o nome do aluno (texto) à nota (double):

var notas = new Dictionary<>();

3. O dicionário tem ["pão"] = 0.75m. O que acontece com precos["pão"] = 0.80m;?

4. O que Console.WriteLine(precos); mostra, com precos sendo um Dictionary<string, decimal>?

5. Dentro de um foreach sobre o dicionário d, você chama d.Add("nova", 1). O que acontece?

6. Depois de remover uma chave e acrescentar outra, em que ordem o foreach mostra as chaves de um Dictionary?

Missão final: o caixa conta as frutas

Missão: o caixa da lojinha registrou as vendas do dia no array vendas. Crie um Dictionary<string, int> chamado contagem, conte quantas vezes cada fruta foi vendida e mostre uma linha por fruta no formato banana: 3. No fim, mostre Frutas diferentes: 3 usando o Count. A ordem das linhas não importa.

banana: 3
maçã: 2
uva: 1
Frutas diferentes: 3
string[] vendas = ["banana", "maçã", "banana", "uva", "maçã", "banana"];

// crie o dicionário contagem aqui

foreach (string fruta in vendas)
{
    // conte a fruta
}

// mostre cada fruta e o total de frutas diferentes
string[] vendas = ["banana", "maçã", "banana", "uva", "maçã", "banana"];

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

foreach (string fruta in vendas)
{
    contagem[fruta] = contagem.GetValueOrDefault(fruta) + 1;
}

foreach (var (fruta, vezes) in contagem)
{
    Console.WriteLine($"{fruta}: {vezes}");
}
Console.WriteLine($"Frutas diferentes: {contagem.Count}");

Resumo

  • Um Dictionary<TKey, TValue> liga cada chave (única) a um valor e acha qualquer chave muito rápido.
  • d[k] = v cria ou troca; d.Add(k, v) só cria e lança ArgumentException se a chave já existe.
  • Ler d[k] com uma chave que não existe lança KeyNotFoundException. Quando a chave pode faltar, use TryGetValue ou GetValueOrDefault.
  • foreach (var (chave, valor) in d) percorre os pares; Keys e Values dão só as chaves ou só os valores. Console.WriteLine(d) mostra só o nome do tipo.
  • A ordem de um Dictionary não é garantida (uma remoção já a muda). Para ordem de chave, use SortedDictionary ou ordene antes de mostrar.
  • Acrescentar chaves dentro do foreach do próprio dicionário dá InvalidOperationException. Anote numa lista e mude depois do laço.