¿Está bien utilizar un int para la clave en una KeyedCollection

Muchas veces necesito una colección de objetos no secuenciales con identificadores numéricos. Me gusta usar KeyedCollection para esto, pero creo que hay un inconveniente serio. Si utiliza un int para la clave, ya no puede acceder a los miembros de la colección por su índice (collection [index] ahora es realmente collection [key]). ¿Es esto un problema lo suficientemente grave como para evitar usar el int como clave? ¿Cuál sería una alternativa preferible? (tal vez int.ToString ()?)

Ya he hecho esto antes sin mayores problemas, pero recientemente me encontré con un desagradable problema donde la serialización xml contra KeyedCollection no funciona not si la clave es un int, debido a un error en .NET .

11

4 Respuestas

Básicamente, debe decidir si los usuarios de la clase pueden confundirse por el hecho de que, por ejemplo, no pueden:

for(int i=0; i=< myCollection.Count; i++)
{
    ... myCollection[i] ...
}

aunque por supuesto pueden usar foreach, o usar un elenco:

for(int i=0; i=< myCollection.Count; i++)
{
    ... ((Collection)myCollection)[i] ...
}

No es una decisión fácil, ya que puede conducir fácilmente a heisenbugs. Decidí permitirlo en una de mis aplicaciones, donde el acceso de los usuarios de la clase era casi exclusivamente por clave.

I'm not sure I'd do so for a shared class library though: in general I'd avoid exposing a KeyedCollection in a public API: instead I would expose IList in a public API, and consumers of the API who need keyed access can define their own internal KeyedCollection with a constructor that takes an IEnumerable and populates the collection with it. This means you can easily build a new KeyedCollection from a list retrieved from an API.

En cuanto a la serialización, también hay un problema de rendimiento que informé a Microsoft Connect : KeyedCollection mantiene un diccionario interno y una lista, y serializa ambos; basta con serializar la lista, ya que el diccionario se puede recrear fácilmente en la deserialización.

Por esta razón, además del error de XmlSerialization, le recomendaría evitar serializar un KeyedCollection; en su lugar, solo serializar la lista KeyedCollection.Items.

No me gusta la sugerencia de ajustar su clave int en otro tipo . Me parece incorrecto agregar complejidad simplemente para que un tipo se pueda usar como un elemento en una colección KeyedCollection. Usaría una clave de cadena (ToString) en lugar de hacer esto; esto es más bien como la clase Colección VB6.

FWIW, pregunté la misma pregunta hace algún tiempo en los foros de MSDN. Hay una respuesta de un miembro del equipo FxCop, pero no hay pautas concluyentes.

7
agregado
Puede desactivar el diccionario a través del umbral = -1
agregado el autor paparazzo, fuente
Creo que estoy de acuerdo con no exponer un KeyedCollection que usa un int para la clave en una API. Esto probablemente llevaría a errores en el otro extremo. Los problemas de serialización son ciertamente una buena razón para no serializar la colección en sí, sino solo los artículos (que es lo que hice en mi caso).
agregado el autor Jon B, fuente
También existe el enfoque de utilizar una clase derivada de KeyedCollection específicamente para claves enteras que incluye un método GetByIndex o ItemAt que usa la colección de elementos internos para obtener el elemento apropiado. Esto le permite aprovechar el KeyedCollection existente, en lugar del enfoque a continuación que implica aumentar una colección.
agregado el autor Thomas S. Trias, fuente

Una solución fácil podría ser ajustar el int en otro tipo para crear un tipo distinto para la resolución de sobrecarga. Si usa una estructura , este contenedor no tiene ninguna sobrecarga adicional:

struct Id {
    public int Value;

    public Id(int value) { Value = value; }

    override int GetHashCode() { return Value.GetHashCode(); }

   //… Equals method.
}
3
agregado
Esta es una buena idea. Sin embargo, siempre es también una buena idea hacer que las estructuras sean inmutables. Esto evita confusiones como las siguientes: ID id; id.Value = 2; Id id2 = id; id2.Value = 4;//¿Qué es id.Value después de esto? Muchos esperarían que fuera 4. (lo siento por el formato, usaría saltos de línea si pudiera)
agregado el autor Neil, fuente

It might be best to add a GetById(int) method to a collection type. Collection can be used instead if you don't need any other key for accessing the contained objects:

public class FooCollection : Collection
 { Dictionary dict = new Dictionary();

   public Foo GetById(int id) { return dict[id]; }

   public bool Contains(int id) { return  dict.Containskey(id);}

   protected override void InsertItem(Foo f)
    { dict[f.Id] = f;
      base.InsertItem(f);
    }

   protected override void ClearItems()
    { dict.Clear();
      base.ClearItems();
    }

   protected override void RemoveItem(int index)
    { dict.Remove(base.Items[index].Id);
      base.RemoveItem(index);
    }

   protected override void SetItem(int index, Foo item)
    { dict.Remove(base.Items[index].Id);
      dict[item.Id] = item;
      base.SetItem(index, item);
    }
 }









 }
2
agregado
Esta es la mejor respuesta
agregado el autor nawfal, fuente

La clave en una KeyedCollection debe ser única y derivable rápidamente del objeto que se recopila. Dada una clase de persona, por ejemplo, podría ser la propiedad SSN o quizás incluso concatenar las propiedades FirstName y LastName (si se sabe que el resultado es único). Si un ID es legítimamente un campo del objeto que se está recopilando, es un candidato válido para la clave. Pero tal vez intente lanzarlo como una cadena en lugar de evitar la colisión.

1
agregado