2013-07-09 17 views
6

Un problema che mi capita spesso di eseguire è la necessità di archiviare una raccolta di oggetti in modo tale da poterli recuperare da un particolare campo/proprietà che è un "indice" univoco per quell'oggetto. Ad esempio, ho un oggetto Person per il quale il campo name è un identificatore univoco e desidero poter recuperare da una raccolta di oggetti Person il Person di cui name="Sax Russell". In Java di solito ottengo questo risultato utilizzando uno Map in cui effettivamente desidero uno Set e sempre utilizzando il campo "indice" dell'oggetto come chiave nella mappa, ad esempio peopleMap.add(myPerson.getName(), myPerson). Stavo pensando di fare la stessa cosa in C# con Dictionary s, in questo modo:Collezione C# indicizzata dalla proprietà?

class Person { 
    public string Name {get; set;} 
    public int Age {get; set;} 
    //... 
} 

Dictionary<string, Person> PersonProducerMethod() { 
    Dictionary<string, Person> people = new Dictionary<string, Person>(); 
    //somehow produce Person instances... 
    people.add(myPerson.Name, myPerson); 
    //... 
    return people; 
} 

void PersonConsumerMethod(Dictionary<string, Person> people, List<string> names) { 
    foreach(var name : names) { 
     person = people[name]; 
     //process person somehow... 
    } 
} 

Tuttavia, questo sembra goffo, e introduce un accoppiamento piuttosto lasco tra le chiavi del Dictionary e dei suoi valori; Dipendo implicitamente da ogni produttore di dizionari Person utilizzando la proprietà Name come chiave con cui memorizzare ogni Person. Non ho alcuna garanzia che l'elemento a people["Sax Russell"] sia in realtà un Person con Name="Sax Russell" a meno che non ricontrollo ogni volta che accedo al dizionario.

Potrebbe esserci un modo per garantire esplicitamente che la mia raccolta di oggetti Person sia indicizzata per nome, utilizzando comparatori di uguaglianza personalizzati e/o query LINQ? È importante che la ricerca rimanga costante, motivo per cui non posso semplicemente usare List.Find o Enumerable.Where. Ho provato a utilizzare un HashSet e a costruirlo con un comparatore di uguaglianza che confronta solo il campo Name degli oggetti che gli vengono assegnati, ma non sembra esserci alcun modo per recuperare gli oggetti Person usando solo il loro nome.

+1

Proprio imbattuto in questo, ma hai considerato i [KeyedCollection] (https: //msdn.microsoft.com/en-us/library/ms132438(v=vs.110).aspx) classe? – ygoe

risposta

3

È possibile creare la propria raccolta supportata da un dizionario per eseguire questa operazione. L'idea è di memorizzare un delegato che accetta una persona e restituisce una stringa leggendo la proprietà Name.

Ecco una soluzione scheletrica di una tale collezione:

public class PropertyMap<K,V> : ICollection<V> { 
    private readonly IDictionary<K,V> dict = new Dictionary<K,V>(); 
    private readonly Func<V,K> key; 
    public PropertyMap(Func<V,K> key) { 
     this.key = key; 
    } 
    public void Add(V v) { 
     dict.Add(key(v)); 
    } 
    // Implement other methods of ICollection 
    public this[K k] { 
     get { return dict[k]; } 
     set { dict[k] = value; } 
    } 
} 

Ecco come usarlo:

PropertyMap<string,Person> mp = new PropertyMap<string,Person>(
    p => p.Name 
); 
mp.Add(p1); 
mp.Add(p2); 
+0

Mi piace. Sicuramente una bella soluzione generica al problema. –

+0

Entrambe le risposte sono buone, ma mi piace quanto questo sia generico e in che modo sfrutta i delegati delle funzioni di C# per incapsulare la definizione di "derivare una chiave da questo oggetto". – Edward

+0

Ciò di cui hai bisogno ora è un provider Linq in modo che Linq to Objects utilizzi l'indice per le ricerche: 'mp.Where (person => person.Name ==" Bob ").FirstOrDefault() ' –

6

Non sono sicuro che ci sia qualcosa di built-in che faccia quello che vuoi, ma non c'è nulla che ti impedisca di avvolgere un dizionario specificando la chiave tu stesso e implementando IList<Person>. La chiave qui (nessun gioco di parole è previsto) è il consumatore non ha accesso al dizionario sottostante in modo da poter essere certi che le chiavi siano accurate.

Parte della implementazione potrebbe essere simile alla seguente, notare l'indicizzatore personalizzato così:

public partial class PersonCollection : IList<Person> 
{ 

    //the underlying dictionary 
    private Dictionary<string, Person> _dictionary; 

    public PersonCollection() 
    { 
     _dictionary = new Dictionary<string, Person>(); 
    } 

    public void Add(Person p) 
    { 
     _dictionary.Add(p.Name, p); 
    } 

    public Person this[string name] 
    { 
     get 
     { 
      return _dictionary[name]; 
     } 
    } 

} 

Come bonus lato, si è anche liberi di modificare l'implementazione in seguito senza dover modificare il codice che consumano.

+0

Questo è esattamente quello che stavo per dire. :) –

Problemi correlati