C# 属性索引的集合?

7
我经常遇到的问题是需要以一种方式存储对象集合,以便我可以通过特定的字段/属性检索它们,该字段/属性是该对象的唯一“索引”。例如,我有一个名为Person的对象,其中name字段是唯一标识符,我希望能够从某些Person对象的集合中检索出name="Sax Russell"Person。在Java中,我通常使用Map来实现这一点,即我实际上想要一个Set,并且始终将对象的“索引”字段用作其在映射中的键,即peopleMap.add(myPerson.getName(), myPerson)。我想在C#中使用Dictionary来做同样的事情,就像这样:
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 in names) {
        person = people[name];
        //process person somehow...
    }
}

然而,这种方法似乎笨拙,并且在 Dictionary 的键和值之间引入了松散耦合;我隐式地依赖于每个 Person 字典的生成者都将 Name 属性作为保存每个 Person 的键。我无法保证 people["Sax Russell"] 处的元素实际上是一个 Name="Sax Russell"Person,除非每次访问字典时都进行双重检查。
也许有一些方法可以显式地确保我的 Person 对象集合通过名称进行索引,使用自定义的相等比较器和/或 LINQ 查询?查找时间必须保持恒定,这就是为什么我不能只使用 List.FindEnumerable.Where。我已经尝试使用 HashSet 并构造了一个只比较其给定对象的 Name 字段的相等比较器,但似乎没有任何办法只使用它们的名称检索 Person 对象。

2
刚刚偶然发现这个,但你是否考虑过KeyedCollection类? - ygoe
3个回答

7
我不确定是否有内置的功能可以满足您的要求,但是没有阻止您自己包装一个指定键的字典,并实现 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];
        }
    }

}

作为一个额外的好处,您还可以自由地更改实现,而无需更改使用代码。

这正是我要说的。 :) - Michael Bray
如果键不唯一怎么办? - Alex from Jitbit
@AlexfromJitbit 这可能有点丑陋,但我通常通过在字典中存储列表来解决它,即 Dictionary<string, List<Person>>。在添加新键时初始化列表;当索引到时返回整个列表。如果您不想将它们公开,可以返回一个 IEnumerable 并在索引器中懒惰地枚举底层列表。 - lc.
@lc 谢谢,但我想到了一个“查找”解决方案,请看下面的答案。 - Alex from Jitbit

4
你可以构建自己的字典支持集合来完成这个任务。想法是存储一个委托,该委托通过读取Name属性以从Person中返回一个字符串。
这是这样一个集合的基本解决方案:
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);

我喜欢这个。绝对是一个不错的通用解决方案。 - lc.
两个答案都不错,但我喜欢这个的通用性,以及它利用C#的函数委托来封装“从此对象派生密钥”的定义。 - Edward
现在您需要的是一个 Linq 提供程序,以便 Linq to Objects 可以使用索引进行查找:mp.Where(person => person.Name == "Bob").FirstOrDefault() - Simon Gillbee

0
如果您的属性值不唯一,但仍想通过该属性对集合进行“索引”(例如按人名对集合进行索引,以便可以快速查找收藏中所有名为“Bob”的人),则可以使用.NET内置的Lookup类,如此问题所述:.NET字典中的重复键?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接