O título é básico o suficiente, por que não posso:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
O título é básico o suficiente, por que não posso:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Respostas:
Um comentário à pergunta original resume isso muito bem:
porque ninguém jamais projetou, especificou, implementou, testou, documentou e enviou esse recurso. - @Gabe Moothart
Por quê? Bem, provavelmente porque o comportamento de mesclar dicionários não pode ser fundamentado de uma maneira que se encaixe nas diretrizes do Framework.
AddRange
não existe porque um intervalo não tem nenhum significado para um contêiner associativo, já que o intervalo de dados permite entradas duplicadas. Por exemplo, se você tinha uma IEnumerable<KeyValuePair<K,T>>
coleção, isso não protege contra entradas duplicadas.
O comportamento de adicionar uma coleção de pares de valores-chave ou mesmo mesclar dois dicionários é direto. O comportamento de como lidar com várias entradas duplicadas, no entanto, não é.
Qual deve ser o comportamento do método ao lidar com uma duplicata?
Posso pensar em pelo menos três soluções:
Quando uma exceção é lançada, qual deve ser o estado do dicionário original?
Add
quase sempre é implementado como uma operação atômica: é bem-sucedido e atualiza o estado da coleção, ou falha, e o estado da coleção permanece inalterado. Como AddRange
pode falhar devido a erros duplicados, a maneira de manter seu comportamento consistente com Add
seria também torná-lo atômico, lançando uma exceção em qualquer duplicata, e deixar o estado do dicionário original inalterado.
Como um consumidor de API, seria tedioso ter que remover iterativamente os elementos duplicados, o que implica que o AddRange
deve lançar uma única exceção que contém todos os valores duplicados.
A escolha então se resume a:
Existem argumentos para apoiar os dois casos de uso. Para fazer isso, você adiciona um IgnoreDuplicates
sinalizador à assinatura?
O IgnoreDuplicates
sinalizador (quando definido como verdadeiro) também forneceria uma velocidade significativa, pois a implementação subjacente ignoraria o código para verificação duplicada.
Agora, você tem um sinalizador que permite que o AddRange
suporte ambos os casos, mas tem um efeito colateral não documentado (que é algo que os designers do Framework trabalharam muito para evitar).
Resumo
Como não há um comportamento claro, consistente e esperado quando se trata de lidar com duplicatas, é mais fácil não lidar com todas elas juntas e não fornecer o método para começar.
Se você está continuamente tendo que mesclar dicionários, pode, é claro, escrever seu próprio método de extensão para mesclar dicionários, que se comportará de uma maneira que funcione para seu (s) aplicativo (s).
AddMultiple
é diferente de AddRange
, independentemente de sua implementação, será difícil: você lança uma exceção com um array de todas as chaves duplicadas? Ou você lança uma exceção na primeira chave duplicada que encontrar? Qual deve ser o estado do dicionário se uma exceção for lançada? Pristine, ou todas as chaves que funcionaram?
Add
- embrulhar cada Add
um em um try...catch
e capturar as duplicatas dessa forma; ou use o indexador e sobrescreva o primeiro valor pelo valor posterior; ou verifique preventivamente usando ContainsKey
antes de tentar Add
, preservando assim o valor original. Se a estrutura tivesse um método AddRange
ou AddMultiple
, a única maneira simples de comunicar o que aconteceu seria por meio de uma exceção, e o manuseio e a recuperação envolvidos não seriam menos complicados.
Eu tenho alguma solução:
Dictionary<string, string> mainDic = new Dictionary<string, string>() {
{ "Key1", "Value1" },
{ "Key2", "Value2.1" },
};
Dictionary<string, string> additionalDic= new Dictionary<string, string>() {
{ "Key2", "Value2.2" },
{ "Key3", "Value3" },
};
mainDic.AddRangeOverride(additionalDic); // Overrides all existing keys
// or
mainDic.AddRangeNewOnly(additionalDic); // Adds new keys only
// or
mainDic.AddRange(additionalDic); // Throws an error if keys already exist
// or
if (!mainDic.ContainsKeys(additionalDic.Keys)) // Checks if keys don't exist
{
mainDic.AddRange(additionalDic);
}
...
namespace MyProject.Helper
{
public static class CollectionHelper
{
public static void AddRangeOverride<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic[x.Key] = x.Value);
}
public static void AddRangeNewOnly<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => { if (!dic.ContainsKey(x.Key)) dic.Add(x.Key, x.Value); });
}
public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dic, IDictionary<TKey, TValue> dicToAdd)
{
dicToAdd.ForEach(x => dic.Add(x.Key, x.Value));
}
public static bool ContainsKeys<TKey, TValue>(this IDictionary<TKey, TValue> dic, IEnumerable<TKey> keys)
{
bool result = false;
keys.ForEachOrBreak((x) => { result = dic.ContainsKey(x); return result; });
return result;
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
foreach (var item in source)
action(item);
}
public static void ForEachOrBreak<T>(this IEnumerable<T> source, Func<T, bool> func)
{
foreach (var item in source)
{
bool result = func(item);
if (result) break;
}
}
}
}
Diverta-se.
ToList()
, um dicionário é um IEnumerable<KeyValuePair<TKey,TValue>
. Além disso, o segundo e o terceiro métodos serão lançados se você adicionar um valor de chave existente. Não é uma boa ideia, você estava procurando TryAdd
? Finalmente, o segundo pode ser substituído porWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()
não é uma boa solução, então mudei o código. Você pode usar try { mainDic.AddRange(addDic); } catch { do something }
se não tiver certeza para o terceiro método. O segundo método funciona perfeitamente.
Caso alguém se depare com esta questão como eu - é possível obter "AddRange" usando os métodos de extensão IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
O principal truque ao combinar dicionários é lidar com as chaves duplicadas. No código acima é a parte .Select(grp => grp.First())
. Nesse caso, ele simplesmente pega o primeiro elemento do grupo de duplicatas, mas você pode implementar uma lógica mais sofisticada lá, se necessário.
dict1
não usar o comparador de igualdade padrão?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Meu palpite é a falta de saída adequada para o usuário quanto ao que aconteceu. Como você não pode ter chaves repetidas em um dicionário, como você lidaria com a fusão de dois dicionários onde algumas chaves se cruzam? Claro, você poderia dizer: "Não me importo", mas isso está quebrando a convenção de retornar false / lançar uma exceção para teclas repetidas.
Add
, além de que pode acontecer mais de uma vez. Seria o mesmo ArgumentException
que joga Add
, certo?
NamedElementException
??), lançado em vez de ou como a innerException de um ArgumentException, que especifica o elemento nomeado em conflito ... algumas opções diferentes, eu diria
Você poderia fazer isso
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
ou use uma lista para adicionar intervalo e / ou usando o padrão acima.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic
. Vem do OP. Reveja a pergunta e a minha resposta novamente.
Se você estiver lidando com um novo Dicionário (e não tiver linhas existentes para perder), você sempre pode usar ToDictionary () de outra lista de objetos.
Então, no seu caso, você faria algo assim:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Se você sabe que não terá chaves duplicadas, pode fazer:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Ele lançará uma exceção se houver um par de chave / valor duplicado.
Não sei por que isso não está na estrutura; deveria estar. Não há incerteza; apenas lance uma exceção. No caso desse código, ele lança uma exceção.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);
?
Sinta-se à vontade para usar o método de extensão como este:
public static Dictionary<T, U> AddRange<T, U>(this Dictionary<T, U> destination, Dictionary<T, U> source)
{
if (destination == null) destination = new Dictionary<T, U>();
foreach (var e in source)
destination.Add(e.Key, e.Value);
return destination;
}
Aqui está uma solução alternativa usando c # 7 ValueTuples (literais de tupla)
public static class DictionaryExtensions
{
public static Dictionary<TKey, TValue> AddRange<TKey, TValue>(this Dictionary<TKey, TValue> source, IEnumerable<ValueTuple<TKey, TValue>> kvps)
{
foreach (var kvp in kvps)
source.Add(kvp.Item1, kvp.Item2);
return source;
}
public static void AddTo<TKey, TValue>(this IEnumerable<ValueTuple<TKey, TValue>> source, Dictionary<TKey, TValue> target)
{
target.AddRange(source);
}
}
Usado como
segments
.Zip(values, (s, v) => (s.AsSpan().StartsWith("{") ? s.Trim('{', '}') : null, v))
.Where(zip => zip.Item1 != null)
.AddTo(queryParams);
Como outros mencionaram, o motivo pelo qual Dictionary<TKey,TVal>.AddRange
não foi implementado é porque existem várias maneiras de lidar com casos em que há duplicatas. Este é também o caso para Collection
ou interfaces, tais como IDictionary<TKey,TVal>
, ICollection<T>
, etc.
Apenas o List<T>
implementa, e você notará que a IList<T>
interface não, pelos mesmos motivos: o esperado comportamento ao adicionar um intervalo de valores a uma coleção pode variar amplamente, dependendo do contexto.
O contexto de sua pergunta sugere que você não está preocupado com duplicatas e, nesse caso, você tem uma alternativa simples de uma linha, usando o Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));