按值排序的字典集合






4.25/5 (10投票s)
2004年5月7日
2分钟阅读

147802

231
一篇关于创建自定义集合的文章,类似于 SortedList,但按值而不是按键排序条目。
引言
本文提供的类说明了如何创建类似于 System.Collections.SortedList
集合的自定义集合,但此处创建的集合将按 value
而不是像 SortedList
那样按 key
排序项目。
背景
在我的一个主要项目中,我创建了一个自定义字段控件,能够根据其 fieldtype
属性呈现实际的 ASP.NET 控件。 其中一种字段类型是 dropdown
,它呈现一个组合框,该组合框绑定到一个 SortedList
作为其值。
此实现效果很好,但很快变得很明显,使用 SortedList
并不是最佳选择,因为组合框中显示的元素是按 key
而不是按 value
排序的。
为了解决这个问题,我搜索了 MSDN 寻找解决方案,并做了一些谷歌搜索。我发现我并不是唯一一个遇到这个问题的人!
我没有找到解决我问题的好的方案,但我找到了 Marc Clifton 写的一篇好文章,我用它作为开发本文中所示集合的路线图。
使用代码
LookupCollection
的使用方式与 Microsoft 提供的 SortedList
完全相同,但如引言中所述,它将按 value
而不是按 key
排序项目。 我使用此集合的主要目的是将其用作 ASP.NET 页面上 DropDownList
的数据源,如下面的代码片段所示
LookupCollection collection = new LookupCollection();
collection.Clear();
collection.Add("002", "Hertzogville");
collection.Add("005", "Bloemfontein");
collection.Add("HER", "Herman");
collection.Add("001", "Kimberley");
collection.Add("012", "Bothaville");
collection.Add("HAN", "Hannes");
this.lstResults.DataSource = collection;
this.lstResults.DataValueField = "key";
this.lstResults.DataTextField = "value";
this.lstResults.DataBind();
单元测试
也许可以通过检查使用 NUnit 开发的单元测试类来更全面地说明如何使用该代码。
using System;
using System.Collections;
using NUnit.Framework;
namespace CustomCollection {
[TestFixture]
public class LookupCollectionTest {
private LookupCollection collection;
//---------------------------------------------------------------------
// Init
//---------------------------------------------------------------------
[SetUp]
public void Init() {
this.collection = new LookupCollection();
}
//---------------------------------------------------------------------
// TestAdd
//---------------------------------------------------------------------
[Test]
public void TestAdd() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("001", "Kimberley");
this.collection.Add("FRA", "Francisca");
Assert.AreEqual(4, this.collection.Count);
this.collection.Clear();
Assert.AreEqual(0, this.collection.Count);
}
//---------------------------------------------------------------------
// TestClear
//---------------------------------------------------------------------
[Test]
public void TestClear() {
this.collection.Add("T1", "Test");
this.collection.Add("TEST", "ATEST");
this.collection.Add("1", "1");
this.collection.Clear();
Assert.AreEqual(0, this.collection.Count);
}
//---------------------------------------------------------------------
// TestContains
//---------------------------------------------------------------------
[Test]
public void TestContains() {
this.collection.Clear();
this.collection.Add("HER", "Herman");
this.collection.Add("HAN", "Hannes");
this.collection.Add("ELA", "Elaine");
this.collection.Add("FRA", "Francisca");
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
Assert.IsTrue(this.collection.Contains("FRA"));
Assert.IsTrue(this.collection.Contains("HAN"));
Assert.IsTrue(this.collection.Contains("002"));
Assert.IsFalse(this.collection.Contains("NON"));
}
//---------------------------------------------------------------------
// TestCopyTo
//---------------------------------------------------------------------
[Test]
public void TestCopyTo() {
this.collection.Clear();
this.collection.Add("HER", "Herman");
this.collection.Add("HAN", "Hannes");
this.collection.Add("ELA", "Elaine");
this.collection.Add("FRA", "Francisca");
Lookup[] testArray = new Lookup[4];
Assert.IsNull(testArray[0]);
Assert.IsNull(testArray[3]);
this.collection.CopyTo(testArray, 0);
Assert.IsNotNull(testArray[0]);
Assert.IsNotNull(testArray[3]);
}
//---------------------------------------------------------------------
// TestGetEnumerator
//---------------------------------------------------------------------
[Test]
public void TestGetEnumerator() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("HER", "Herman");
this.collection.Add("001", "Kimberley");
this.collection.Add("012", "Bothaville");
this.collection.Add("HAN", "Hannes");
IDictionaryEnumerator enumurator = this.collection.GetEnumerator();
enumurator.MoveNext(); Assert.AreEqual("Bloemfontein",
((Lookup)enumurator.Current).Value);
enumurator.MoveNext(); Assert.AreEqual("Bothaville",
((Lookup)enumurator.Current).Value);
enumurator.MoveNext(); Assert.AreEqual("Hannes",
((Lookup)enumurator.Current).Value);
enumurator.MoveNext(); Assert.AreEqual("Herman",
((Lookup)enumurator.Current).Value);
enumurator.MoveNext(); Assert.AreEqual("Hertzogville",
((Lookup)enumurator.Current).Value);
enumurator.MoveNext(); Assert.AreEqual("Kimberley",
((Lookup)enumurator.Current).Value);
}
//---------------------------------------------------------------------
// TestRemove
//---------------------------------------------------------------------
[Test]
public void TestRemove() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("001", "Kimberley");
this.collection.Remove("005");
Assert.AreEqual(2, this.collection.Count);
this.collection.Remove("004");
Assert.AreEqual(2, this.collection.Count);
this.collection.Remove("002");
Assert.AreEqual(1, this.collection.Count);
}
//=====================================================================
// PROPERTIES
//=====================================================================
//---------------------------------------------------------------------
// TestCount
//---------------------------------------------------------------------
[Test]
public void TestCount() {
this.collection.Clear();
Assert.AreEqual(this.collection.Count, 0);
for (int i = 0; i < 100; i++) {
this.collection.Add(i.ToString(), i + " Item");
}
Assert.AreEqual(this.collection.Count, 100);
}
//---------------------------------------------------------------------
// TestIndexer
//---------------------------------------------------------------------
[Test]
public void TestIndexer() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("001", "Kimberley");
this.collection.Add("012", "Bothaville");
Assert.IsTrue(this.collection.Contains("001"));
Assert.AreEqual("Kimberley", this.collection["001"]);
Assert.IsTrue(this.collection.Contains("002"));
Assert.AreEqual("Hertzogville", this.collection["002"]);
}
//---------------------------------------------------------------------
// TestKeys
//---------------------------------------------------------------------
[Test]
public void TestKeys() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("HER", "Herman");
this.collection.Add("001", "Kimberley");
this.collection.Add("012", "Bothaville");
this.collection.Add("HAN", "Hannes");
ArrayList keys = (ArrayList)this.collection.Keys;
Assert.AreEqual("005", keys[0]);
Assert.AreEqual("012", keys[1]);
Assert.AreEqual("HAN", keys[2]);
Assert.AreEqual("HER", keys[3]);
Assert.AreEqual("002", keys[4]);
Assert.AreEqual("001", keys[5]);
}
//---------------------------------------------------------------------
// TestValues
//---------------------------------------------------------------------
[Test]
public void TestValues() {
this.collection.Clear();
this.collection.Add("002", "Hertzogville");
this.collection.Add("005", "Bloemfontein");
this.collection.Add("HER", "Herman");
this.collection.Add("001", "Kimberley");
this.collection.Add("012", "Bothaville");
this.collection.Add("HAN", "Hannes");
ArrayList values = (ArrayList)this.collection.Values;
Assert.AreEqual("Bloemfontein", values[0]);
Assert.AreEqual("Bothaville", values[1]);
Assert.AreEqual("Hannes", values[2]);
Assert.AreEqual("Herman", values[3]);
Assert.AreEqual("Hertzogville", values[4]);
Assert.AreEqual("Kimberley", values[5]);
}
}
}
查找
下面说明的 Lookup
类用于在 LookupCollection
中存储键值对,但我确实认为(希望)一些聪明的程序员会对 ToDictionaryEntry
和 CompareTo
的实现有所见解,并且迫不及待地想收到反馈!
using System;
using System.Collections;
namespace CustomCollection {
[Serializable]
public class Lookup : IComparable {
private object mKey;
private object mValue;
//---------------------------------------------------------------------
// Default Constructor
//---------------------------------------------------------------------
public Lookup() : this(null, null) {
}
//---------------------------------------------------------------------
// Overloaded Constructor
//---------------------------------------------------------------------
public Lookup(object key, object value) {
this.Key = key;
this.Value = value;
}
//---------------------------------------------------------------------
// CompareTo
//---------------------------------------------------------------------
public int CompareTo(object obj) {
int result = 0;
if (obj is Lookup) {
result =
((IComparable)this.Value).CompareTo((IComparable)
(((Lookup)obj).Value));
}
return result;
}
//---------------------------------------------------------------------
// ToDictionaryEntry
//---------------------------------------------------------------------
public DictionaryEntry ToDictionaryEntry() {
return new DictionaryEntry(this.Key, this.Value);
}
//=====================================================================
// PROPERTIES
//=====================================================================
public object Key {
get {
return this.mKey;
}
set {
if (this.mKey != value) {
this.mKey = value;
}
}
}
public object Value {
get {
return this.mValue;
}
set {
if (this.mValue != value) {
this.mValue = value;
}
}
}
}
}
枚举器
枚举器类由 LookupCollection
类使用,以创建一个枚举器,允许程序员在此集合上使用 foreach
循环。
using System;
using System.Collections;
namespace CustomCollection {
public class LookupEnumerator : IDictionaryEnumerator {
private int index = -1;
private ArrayList items;
//---------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------
public LookupEnumerator(ArrayList list) {
this.items = list;
}
//---------------------------------------------------------------------
// MoveNext
//---------------------------------------------------------------------
public bool MoveNext() {
this.index++;
if (index >= this.items.Count)
return false;
return true;
}
//=====================================================================
// PROPERTIES
//=====================================================================
//---------------------------------------------------------------------
// Reset
//---------------------------------------------------------------------
public void Reset() {
this.index = -1;
}
//---------------------------------------------------------------------
// Current
//---------------------------------------------------------------------
public object Current {
get {
if (this.index < 0 || index >= this.items.Count)
throw new InvalidOperationException();
return this.items[index];
}
}
//---------------------------------------------------------------------
// Entry
//---------------------------------------------------------------------
public DictionaryEntry Entry {
get {
return ((Lookup)this.Current).ToDictionaryEntry();
}
}
//---------------------------------------------------------------------
// Key
//---------------------------------------------------------------------
public object Key {
get {
return this.Entry.Key;
}
}
//---------------------------------------------------------------------
// Value
//---------------------------------------------------------------------
public object Value {
get {
return this.Entry.Value;
}
}
}
}
LookupCollection
这是实现本文所讨论的集合的实际类。 从代码中可以看出,添加到此集合中的字典条目内部存储在 Lookup
项目的 ArrayList
中。
using System;
using System.Collections;
namespace CustomCollection {
[Serializable]
public class LookupCollection : ICollection, IDictionary, IEnumerable {
private ArrayList mItems = new ArrayList();
//---------------------------------------------------------------------
// Constructor
//---------------------------------------------------------------------
public LookupCollection() {
}
//---------------------------------------------------------------------
// Add
//---------------------------------------------------------------------
public void Add(object key, object value) {
// do some validation
if (key == null)
throw new ArgumentNullException("key is a null reference");
else if (this.Contains(key))
throw new
ArgumentException("An element with the same key already exists");
// add the new item
Lookup newItem = new Lookup();
newItem.Key = key;
newItem.Value = value;
this.mItems.Add(newItem);
this.mItems.Sort();
}
//---------------------------------------------------------------------
// Clear
//---------------------------------------------------------------------
public void Clear() {
this.mItems.Clear();
}
//---------------------------------------------------------------------
// Contains
//---------------------------------------------------------------------
public bool Contains(object key) {
return (this.GetByKey(key) != null);
}
//---------------------------------------------------------------------
// CopyTo
//---------------------------------------------------------------------
public void CopyTo(Array array, int index) {
this.mItems.CopyTo(array, index);
}
//---------------------------------------------------------------------
// GetEnumerator (1)
//---------------------------------------------------------------------
public IDictionaryEnumerator GetEnumerator() {
return new LookupEnumerator(this.mItems);
}
//---------------------------------------------------------------------
// GetEnumerator (2)
//---------------------------------------------------------------------
IEnumerator IEnumerable.GetEnumerator() {
return new LookupEnumerator(this.mItems);
}
//---------------------------------------------------------------------
// Remove
//---------------------------------------------------------------------
public void Remove(object key) {
if (key == null)
throw new ArgumentNullException("key is a null reference");
Lookup deleteItem = this.GetByKey(key);
if (deleteItem != null) {
this.mItems.Remove(deleteItem);
this.mItems.Sort();
}
}
//=====================================================================
// PRIVATE
//=====================================================================
private Lookup GetByKey(object key) {
Lookup result = null;
int keyIndex = -1;
ArrayList keys = (ArrayList)this.Keys;
if (this.mItems.Count > 0) {
keyIndex = keys.IndexOf(key);
if (keyIndex >= 0) {
result = (Lookup)this.mItems[keyIndex];
}
}
return result;
}
//=====================================================================
// PROPERTIES
//=====================================================================
public int Count {
get {
return this.mItems.Count;
}
}
public bool IsSynchronized {
get {
return false;
}
}
public object SyncRoot {
get {
return this;
}
}
public bool IsFixedSize {
get {
return false;
}
}
public bool IsReadOnly {
get {
return false;
}
}
public object this[object key] {
get {
if (key == null)
throw new ArgumentNullException("key is a null reference");
object result = null;
Lookup findItem = this.GetByKey(key);
if (findItem != null) {
result = findItem.Value;
}
return result;
}
set {
}
}
public ICollection Keys {
get {
ArrayList result = new ArrayList();
this.mItems.Sort();
foreach (Lookup curItem in this.mItems) {
result.Add(curItem.Key);
}
return result;
}
}
public ICollection Values {
get {
ArrayList result = new ArrayList();
foreach (Lookup curItem in this.mItems) {
result.Add(curItem.Value);
}
return result;
}
}
}
}
结论
尽管我非常确定我的实现不是最好的,但我希望您发现这些类至少在某种程度上是有用的,或者它们将帮助您指向正确的方向。