El título es lo suficientemente básico, ¿por qué no puedo:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
El título es lo suficientemente básico, ¿por qué no puedo:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.AddRange(MethodThatReturnAnotherDic());
Respuestas:
Un comentario a la pregunta original lo resume bastante bien:
porque nadie diseñó, especificó, implementó, probó, documentó y envió esa característica. - @Gabe Moothart
¿Como qué? Bueno, probablemente porque el comportamiento de combinar diccionarios no se puede razonar de una manera que se ajuste a las pautas del Framework.
AddRangeno existe porque un rango no tiene ningún significado para un contenedor asociativo, ya que el rango de datos permite entradas duplicadas. Por ejemplo, si tiene una IEnumerable<KeyValuePair<K,T>>colección que no protege contra entradas duplicadas.
El comportamiento de agregar una colección de pares clave-valor, o incluso fusionar dos diccionarios, es sencillo. Sin embargo, el comportamiento de cómo lidiar con múltiples entradas duplicadas no lo es.
¿Cuál debería ser el comportamiento del método cuando se trata de un duplicado?
Hay al menos tres soluciones en las que puedo pensar:
Cuando se lanza una excepción, ¿cuál debería ser el estado del diccionario original?
Addcasi siempre se implementa como una operación atómica: tiene éxito y actualiza el estado de la colección, o falla, y el estado de la colección no cambia. Como AddRangepuede fallar debido a errores duplicados, la forma de mantener su comportamiento consistente con Addsería hacerlo también atómico lanzando una excepción en cualquier duplicado, y dejar el estado del diccionario original sin cambios.
Como consumidor de API, sería tedioso tener que eliminar iterativamente elementos duplicados, lo que implica que AddRangedebería lanzar una única excepción que contenga todos los valores duplicados.
La elección luego se reduce a:
Hay argumentos para respaldar ambos casos de uso. Para hacer eso, ¿agregas una IgnoreDuplicatesbandera a la firma?
La IgnoreDuplicatesbandera (cuando se establece en verdadero) también proporcionaría una aceleración significativa, ya que la implementación subyacente omitiría el código para la verificación duplicada.
Así que ahora, tiene una bandera que le permite AddRangeadmitir ambos casos, pero tiene un efecto secundario no documentado (que es algo que los diseñadores de Framework trabajaron muy duro para evitar).
Resumen
Como no existe un comportamiento claro, coherente y esperado cuando se trata de tratar con duplicados, es más fácil no tratarlos todos juntos y no proporcionar el método para empezar.
Si tiene que fusionar diccionarios continuamente, por supuesto, puede escribir su propio método de extensión para fusionar diccionarios, que se comportará de una manera que funcione para su (s) aplicación (es).
AddMultiplees diferente a AddRange, independientemente de que su implementación sea inestable: ¿Lanzas una excepción con una matriz de todas las claves duplicadas? ¿O lanza una excepción en la primera clave duplicada que encuentra? ¿Cuál debería ser el estado del diccionario si se lanza una excepción? ¿Prístino, o todas las llaves que tuvieron éxito?
Add, ya sea envolver cada uno Adden a try...catchy capturar los duplicados de esa manera; o use el indexador y sobrescriba el primer valor con el valor posterior; o comprobar de forma preventiva el uso ContainsKeyantes de intentarlo Add, conservando así el valor original. Si el marco tuviera un método AddRangeo AddMultiple, la única forma sencilla de comunicar lo sucedido sería mediante una excepción, y el manejo y la recuperación involucrados no serían menos complicados.
Tengo alguna solución:
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;
}
}
}
}
Que te diviertas.
ToList(), un diccionario es un IEnumerable<KeyValuePair<TKey,TValue>. Además, los métodos del segundo y tercer método se lanzarán si agrega un valor de clave existente. No es una buena idea, ¿estabas buscando TryAdd? Finalmente, el segundo se puede reemplazar conWhere(pair->!dic.ContainsKey(pair.Key)...
ToList()no es una buena solución, así que cambié el código. Puede usarlo try { mainDic.AddRange(addDic); } catch { do something }si no está seguro del tercer método. El segundo método funciona perfectamente.
En caso de que alguien se encuentre con esta pregunta como yo, es posible lograr "AddRange" mediante el uso de métodos de extensión IEnumerable:
var combined =
dict1.Union(dict2)
.GroupBy(kvp => kvp.Key)
.Select(grp => grp.First())
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
El principal truco al combinar diccionarios es lidiar con las claves duplicadas. En el código de arriba está la parte .Select(grp => grp.First()). En este caso, simplemente toma el primer elemento del grupo de duplicados, pero puede implementar una lógica más sofisticada allí si es necesario.
dict1 no usa el comparador de igualdad predeterminado?
var combined = dict1.Concat(dict2).GroupBy(kvp => kvp.Key, dict1.Comparer).ToDictionary(grp => grp.Key, grp=> grp.First(), dict1.Comparer);
Mi conjetura es la falta de una salida adecuada para el usuario sobre lo que sucedió. Como no puede tener claves repetidas en un diccionario, ¿cómo manejaría fusionar dos diccionarios donde algunas claves se cruzan? Seguro que podría decir: "No me importa", pero eso está rompiendo la convención de devolver falso / lanzar una excepción para las claves repetidas.
Add, aparte de que puede suceder más de una vez? Tiraría lo mismo ArgumentExceptionque Addhace, ¿seguro?
NamedElementException??), ya sea lanzada en lugar de o como la innerException de una ArgumentException, que especifica el elemento con nombre que entra en conflicto ... algunas opciones diferentes, diría yo
Podrías hacer esto
Dictionary<string, string> dic = new Dictionary<string, string>();
// dictionary other items already added.
MethodThatReturnAnotherDic(dic);
public void MethodThatReturnAnotherDic(Dictionary<string, string> dic)
{
dic.Add(.., ..);
}
o use una Lista para agregar rango y / o usando el patrón anterior.
List<KeyValuePair<string, string>>
MethodThatReturnAnotherDic. Viene del OP. Por favor revise la pregunta y mi respuesta nuevamente.
Si está tratando con un nuevo diccionario (y no tiene filas existentes que perder), siempre puede usar ToDictionary () de otra lista de objetos.
Entonces, en tu caso, harías algo como esto:
Dictionary<string, string> dic = new Dictionary<string, string>();
dic = SomeList.ToDictionary(x => x.Attribute1, x => x.Attribute2);
Dictionary<string, string> dic = SomeList.ToDictionary...
Si sabe que no va a tener claves duplicadas, puede hacer lo siguiente:
dic = dic.Union(MethodThatReturnAnotherDic()).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
Lanzará una excepción si hay un par clave / valor duplicado.
No sé por qué esto no está en el marco; debiera ser. No hay incertidumbre; solo lanza una excepción. En el caso de este código, lanza una excepción.
var caseInsensitiveDictionary = new Dictionary<string, int>( StringComparer.OrdinalIgnoreCase);?
Siéntase libre de usar un método de extensión 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;
}
Aquí hay una solución alternativa usando c # 7 ValueTuples (literales 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 han mencionado otros, la razón por la Dictionary<TKey,TVal>.AddRangeque no se implementa es porque hay varias formas en las que puede querer manejar los casos en los que tiene duplicados. Este es también el caso de Collectiono interfaces tales como IDictionary<TKey,TVal>, ICollection<T>, etc.
Solo lo List<T>implementa, y notará que la IList<T>interfaz no lo hace, por las mismas razones: el comportamiento esperado al agregar un rango de valores a una colección puede variar ampliamente, dependiendo del contexto.
El contexto de su pregunta sugiere que no le preocupan los duplicados, en cuyo caso tiene una alternativa simple de un delineador, usando Linq:
MethodThatReturnAnotherDic().ToList.ForEach(kvp => dic.Add(kvp.Key, kvp.Value));