É OK usar um int para a chave em um KeyedCollection

Muitas vezes eu preciso de uma coleção de objetos não seqüenciais com identificadores numéricos. Eu gosto de usar o KeyedCollection para isso, mas acho que há uma séria desvantagem. Se você usar um int para a chave, não poderá mais acessar os membros da coleção pelo índice (coleção [index] agora é realmente coleção [chave]). Este é um problema sério o suficiente para evitar usar o int como chave? Qual seria a alternativa preferível? (talvez int.ToString ()?)

Eu fiz isso antes, sem grandes problemas, mas recentemente eu bati um problema desagradável onde serialização xml contra um KeyedCollection não funciona se a chave é um int, devido a um bug no .NET .

11

4 Respostas

Basicamente, você precisa decidir se os usuários da classe provavelmente ficarão confusos com o fato de que eles não podem, por exemplo:

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

embora eles possam usar naturalmente foreach, ou usar um elenco:

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

Não é uma decisão fácil, pois pode facilmente levar a heisenbugs. Eu decidi permitir isso em um dos meus aplicativos, onde o acesso dos usuários da aula era quase exclusivamente por chave.

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.

Em relação à serialização, há também um problema de desempenho que relatei ao Microsoft Connect /a>: o KeyedCollection mantém um dicionário interno, bem como uma lista, e serializa ambos - é suficiente para serializar a lista, pois o dicionário pode ser facilmente recriado na desserialização.

Por esse motivo, bem como o erro XmlSerialization, recomendo que você evite serializar um KeyedCollection - em vez disso, apenas serialize a lista KeyedCollection.Items.

Não gosto de

a sugestão de colocar sua chave int em outro tipo . Parece-me errado adicionar complexidade simplesmente para que um tipo possa ser usado como um item em um KeyedCollection. Eu usaria uma chave de seqüência de caracteres (ToString) em vez de fazer isso - isso é como a classe VB6 Collection.

FWIW, perguntei ao mesma pergunta há algum tempo atrás nos fóruns do MSDN. Há uma resposta de um membro da equipe FxCop, mas não há diretrizes conclusivas.

7
adicionado
Você pode desativar o dicionário através do limite = -1
adicionado o autor paparazzo, fonte
Acho que concordo com a não exposição de um KeyedCollection que usa um int para a chave em uma API. Isso provavelmente levaria a erros do outro lado. Os problemas de serialização são certamente uma boa razão para não serializar a coleção em si, mas apenas os itens (que é o que eu fiz no meu caso).
adicionado o autor Jon B, fonte
Há também a abordagem de usar uma classe derivada de KeyedCollection especificamente para chaves de número inteiro, que inclui um método GetByIndex ou ItemAt que usa a coleção de itens internos para obter o item apropriado. Isso permite aproveitar o KeyedCollection existente, em vez da abordagem abaixo, que envolve o aumento de uma coleção.
adicionado o autor Thomas S. Trias, fonte

Uma solução fácil pode ser envolver o int em outro tipo para criar um tipo distinto para a resolução de sobrecarga. Se você usar um struct , este wrapper não terá nenhum overhead adicional:

struct Id {
    public int Value;

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

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

   //… Equals method.
}
3
adicionado
Essa é uma boa ideia. No entanto, também é sempre uma boa ideia tornar as estruturas imutáveis. Isso evita confusão como o seguinte: Id id; id.Valor = 2; Id id2 = id; id2.Value = 4;//O que é id.Valor depois disso? Muitos esperariam que fosse 4. (desculpe pela formatação; eu usaria quebras de linha se eu pudesse)
adicionado o autor Neil, fonte

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
adicionado
Esta é a melhor resposta imo
adicionado o autor nawfal, fonte

A chave em um KeyedCollection deve ser exclusiva e rapidamente derivável do objeto que está sendo coletado. Dada uma classe de pessoa, por exemplo, pode ser a propriedade SSN ou talvez concatenar as propriedades FirstName e LastName (se o resultado for conhecido como exclusivo). Se um ID for legitimamente um campo do objeto que está sendo coletado, ele será um candidato válido para a chave. Mas talvez tente lançá-lo como uma string para evitar a colisão.

1
adicionado