2013-07-09 85 views
6

我經常碰到的一個問題是需要以這樣一種方式存儲對象集合,以便我可以通過特定的字段/屬性(它是該對象的唯一「索引」)來檢索它們。例如,我有一個Person對象,其中name字段是唯一標識符,並且我希望能夠從某個Person對象集合中檢索Person的對象name="Sax Russell"。在Java中,我通常通過使用Map來實現這一點,其中我實際上需要Set,並始終使用對象的「索引」字段作爲其在映射中的鍵,即peopleMap.add(myPerson.getName(), myPerson)。我想用Dictionary在做在C#同樣的事情,就像這樣:按屬性索引的C#集合?

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... 
    } 
} 

然而,這似乎笨拙,並介紹了Dictionary和值的鍵之間的相當鬆散的耦合;我隱含地依靠每個Person字典的生產者使用Name屬性作爲存儲每個Person的密鑰。我不能保證people["Sax Russell"]的元素實際上是PersonName="Sax Russell",除非我每次訪問字典時都仔細檢查。

可能有某種方法可以使用自定義相等比較器和/或LINQ查詢來明確確保我的集合中的Person對象按名稱編入索引?查找保持恆定時間非常重要,這就是爲什麼我不能只使用List.FindEnumerable.Where。我嘗試過使用HashSet並使用相等比較器構造它,該比較器只比較它給出的對象的Name字段,但似乎沒有任何方法可以使用它們的名稱來檢索Person對象。

+1

只是偶然發現了這一點,但你有沒有考慮到[KeyedCollection(HTTPS: //msdn.microsoft.com/en-us/library/ms132438(v=vs.110).aspx)class? – ygoe

回答

3

您可以構建您自己的由字典支持的集合以完成此任務。這個想法是存儲一個接受Person並通過讀取Name屬性返回一個字符串的委託。

下面是這樣一個集合的骨骼解決方案:

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; } 
    } 
} 

這裏是如何使用它:

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

我喜歡這個。絕對是一個很好的通用解決方案。 –

+0

這兩個答案都很好,但我喜歡它的通用性,以及它如何利用C#的函數委託來封裝「從此對象派生密鑰」的定義。 – Edward

+0

你現在需要的是一個Linq提供程序,以便Linq到Objects將使用索引進行查找:'mp.Where(person => person.Name ==「Bob」)。FirstOrDefault()' –

6

我不確定是否有任何內置的功能可以滿足您的需求,但沒有任何東西可以阻止您自己封裝指定密鑰的字典並執行IList<Person>。這裏的關鍵(沒有雙關語意思)是消費者不能訪問底層字典,所以你可以放心,關鍵是準確的。實施

部分可能看起來像以下,注意自定義索引,以及:

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]; 
     } 
    } 

} 

作爲一個附帶的好處,你也可以自由後改變實現,而無需改變消費代碼。

+0

這正是我要說的。 :) –