Este OK să utilizați un int pentru cheia într-o cheie KeyCollection

De multe ori am nevoie de o colecție de obiecte nesecvențiale cu identificatori numerici. Îmi place să folosesc KeyedCollection pentru asta, dar cred că există un dezavantaj serios. Dacă utilizați un int pentru cheie, nu mai puteți accesa membrii colecției prin indexul lor (colecția [index] este acum într-adevăr colecție [cheie]). Este o problemă suficient de serioasă pentru a evita utilizarea int ca cheie? Ce ar fi o alternativă preferabilă? (poate int.ToString ()?)

Am facut acest lucru inainte fara probleme majore, dar recent am lovit un snag incorect in cazul in care serializarea xml impotriva unei KeyedCollection nu functioneaza daca cheia este int, din cauza o eroare în .NET .

11

4 răspunsuri

Practic, trebuie să decideți dacă utilizatorii clasei pot fi confundați de faptul că nu pot, de exemplu:

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

deși ei pot folosi, bineînțeles, foreach-ul sau folosesc o distribuție:

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

Nu este o decizie ușoară, deoarece poate duce cu ușurință la heisenbugs. Am decis să o permit într-una din aplicațiile mele, unde accesul utilizatorilor din clasă a fost aproape exclusiv prin cheie.

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.

În ceea ce privește serializarea, există și o problemă de performanță pe care am raportat-o ​​Microsoft Connect : KeyedCollection menține un dicționar intern, precum și o listă și serializează ambele - este suficient să se serializeze lista deoarece dicționarul poate fi recreat cu ușurință prin deserializare.

Din acest motiv, precum și bug-ul XmlSerialization, aș recomanda evitarea serializării unei KeyedCollection - în schimb, doar serializați lista KeyedCollection.Items.

Nu-mi place sugestia de a împacheta tasta int într-un alt tip . Mi se pare greșit să adaug complexitatea pur și simplu, astfel încât un tip poate fi folosit ca element într-o cheie KeyCollection. Aș folosi o cheie de șir (ToString) mai degrabă decât să fac acest lucru - aceasta este mai degrabă ca și clasa VB6 Collection.

FWIW, am cerut aceeași întrebare ceva timp în urmă pe forumurile MSDN. Există un răspuns de la un membru al echipei FxCop, dar nu există recomandări concludente.

7
adăugat
Puteți dezactiva dicționarul prin prag = -1
adăugat autor paparazzo, sursa
Cred că sunt de acord să nu expun un KeyedCollection care folosește un int pentru cheia într-un API. Acest lucru ar duce probabil la bug-uri la celălalt capăt. Problemele de serializare sunt cu siguranță un motiv bun pentru a nu serializa colecția însăși, ci doar elementele (ceea ce am făcut în cazul meu).
adăugat autor Jon B, sursa
Există, de asemenea, abordarea utilizării unei clase derivate din KeyedCollection special pentru cheile întregi care include o metodă GetByIndex sau ItemAt care folosește colecția de elemente interne pentru a obține elementul corespunzător. Acest lucru vă permite să utilizați KeyedCollection existent, în locul abordării de mai jos, care implică augmentarea unei Colecții.
adăugat autor Thomas S. Trias, sursa

O soluție ușoară ar putea fi împachetarea codului int într-un alt tip pentru a crea un tip distinct pentru rezoluția de suprasarcină. Dacă utilizați un struct , această împachetare nu are nicio cheltuială suplimentară:

struct Id {
    public int Value;

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

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

   //… Equals method.
}
3
adăugat
Aceasta este o idee bună. Cu toate acestea, este întotdeauna, de asemenea, o idee bună ca structurile să devină imuabile. Acest lucru evită confuzia, după cum urmează: Id id; id.Value = 2; Id id2 = id; id2.Value = 4;//Ce este id.Value după asta? Mulți ar aștepta să fie 4. (Îmi pare rău pentru formatare, aș folosi pauze de linie dacă aș putea)
adăugat autor Neil, sursa

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
adăugat
Acesta este cel mai bun răspuns imo
adăugat autor nawfal, sursa

Cheia într-o cheie KeyedCollection trebuie să fie unică și să poată deriva rapid din obiectul care este colectat. Datorită unei clase de persoană, de exemplu, ar putea fi proprietatea SSN sau poate chiar concatenarea proprietăților FirstName și LastName (dacă rezultatul este cunoscut a fi unic). Dacă un ID este legitim un câmp al obiectului care este colectat decât un candidat valabil pentru cheie. Dar, probabil, încercați să-l aruncați ca un șir, pentru a evita coliziunea.

1
adăugat