2011-04-06 11 views
18

Estoy creando un objeto Dictionary, utilizando IEnumerable 's método ToDictionary() extensión:¿Cómo se obtiene la clave duplicada que ToDictionary() ha fallado?

var dictionary = new Dictionary<string, MyType> 
    (myCollection.ToDictionary<MyType, string>(k => k.Key)); 

Cuando se ejecuta, que arroja el siguiente ArgumentException:

un elemento con la misma clave ya ha sido adicional.

¿Cómo consigo que me diga cuál es la clave duplicada?

+2

Esta no es una respuesta a su pregunta, pero ToDictionary realmente crea un diccionario. ¿Por qué pasas ese diccionario al constructor de otro diccionario? ¿Y por qué especificar los parámetros genéricos de tipo que no es necesario? Escribiría su ejemplo sin todo ese bagaje como: 'var dictionary = myCollection.ToDictionary (x => x.Key);' –

+0

1. Porque esa es una de las sobrecargas del constructor para Dictionary. Pero tienes un punto. –

+0

2. Presumiblemente, especificar los tipos elimina cualquier posibilidad de boxeo, pero no estoy seguro de eso. –

Respuesta

16

conseguir las llaves duplicadas:

var duplicateKeys = 
    myCollection 
.GroupBy(k => k.Key) 
.Where(g => g.Count() > 1) 
.Select(g => g.Key); 
+2

Parece extraño que la 'ArgumentException' lanzada por' ToDictionary() 'no incluya la clave fallida. ¿Qué tan costoso es tu consulta? La colección es bastante grande. –

+0

@Robert Harvey, en realidad eso es perfectamente normal: 'ArgumentException' es una excepción muy general, no es específica de los diccionarios ... –

+0

@Robert Harvey: La agrupación es bastante eficiente, pero con una gran colección, por supuesto, seguirá siendo una mucho trabajo. Se puede hacer de manera más eficiente usando un 'Diccionario 'y solo contar las ocurrencias de cada tecla en lugar de agruparlas. – Guffa

5

La clave fallado no se incluye porque el diccionario genérico tiene ninguna garantía de que no hay un método ToString significativa en el tipo de clave. Puede crear una clase contenedora que arroje una excepción más informativa. Por ejemplo:

//Don't want to declare the key as type K because I assume _inner will be a Dictionary<string, V> 
//public void Add(K key, V value) 
// 
public void Add(string key, V value) 
{ 
    try 
    { 
     _inner.Add(key, value); 
    } 
    catch (ArgumentException e) 
    { 
     throw new ArgumentException("Exception adding key '" + key + "'", e); 
    } 
} 
+0

Esa es una idea interesante. –

+0

Debería haber hecho la firma 'void Add (clave de cadena, valor V)' ya que mi sugerencia asume que usted solo está interesado en las claves de cadena. Respuesta editada. – phoog

+0

('porque el diccionario genérico no tiene garantía ...' ¿Por qué necesitaría una garantía?) – Iain

1

El ArgumentException ser arrojado por la llamada a Dictionary.Add no contiene el valor de la clave. Se podría añadir muy fácilmente a las entradas de un diccionario a sí mismo, y hacer una verificación clara de antemano:

var dictionary = new Dictionary<string, MyType>(); 
    foreach (var item in myCollection) 
    { 
     string key = item.Key; 
     if (dictionary.ContainsKey(key)) 
     { 
      // Handle error 
      Debug.Fail(string.Format("Found duplicate key: {0}", key)); 
     } 
     else 
     { 
      dictionary.Add(key, item); 
     } 
    } 

Esta comprobación adicional debe ser bastante barato, porque los elementos son almacenados por hash.

+0

Sí, es cierto. Creo que estaba siendo flojo con el método 'ToDictionary()'. –

+0

Dictionary.Add dice que arroja una clave duplicada. Por lo tanto, puede (?) Obtener un beneficio de rendimiento al hacer el Add inmediatamente y atrapar si arroja. – Iain

+2

Capturar una excepción a propósito es casi siempre una mala idea. En cuanto al rendimiento, lanzar y atrapar una excepción es tan complejo que la mayoría de las veces mejorará el rendimiento agregando un control adicional. –

8

Si su situación específica hace que sea bien sólo se inserte una de un conjunto de objetos duplicados con Key propiedades en su diccionario, puede evitar este error en su totalidad mediante el método de LINQ Distinct antes de llamar ToDictionary.

var dict = myCollection.Distinct().ToDictionary(x => x.Key); 

Por supuesto, lo anterior sólo funcionará si las clases en su colección de anulación Equals y GetHashCode de una manera que sólo se necesita la propiedad Key en cuenta. Si ese no es el caso, deberá realizar un pedido personalizado IEqualityComparer<YourClass> que solo compare la propiedad Key.

var comparer = new MyClassKeyComparer(); 
var dict = myCollection.Distinct(comparer).ToDictionary(x => x.Key); 

Si usted necesita para asegurarse de que todas las instancias de su colección terminan en el diccionario, a continuación, utilizando Distinct no va a funcionar para usted.

+0

Gracias, una solución perfecta para mi situación – PandaWood

Cuestiones relacionadas