65.9K
CodeProject 正在变化。 阅读更多。
Home

NameValueCollection 的通用形式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (14投票s)

2022年1月26日

MIT

3分钟阅读

viewsIcon

21282

downloadIcon

377

表示 NameValueCollection 的 C# 通用实现。

目录

  1. 引言
  2. 背景
  3. 用法
  4. 历史

引言

NameValueCollection 在 mscorlib 中的实现是一个类似于 Dictionary<string,string> 的集合,但 NameValueCollection 允许重复的键,而 Dictionary<string,string> 不允许。 可以通过索引和键获取元素。
这个集合的特别之处在于,一个键可以包含多个元素,并且允许使用 null 作为键或值。 但存在一个小问题。 NameValueCollection 假定 string 用作键和值。 那么,如果您想要存储任何类型的值,而不仅仅是 string,该怎么办? 当然,您可以在每次获取值时将文本转换为所需的类型,但这里有三个重要的限制:

  1. 转换的处理开销;
  2. 并非所有类型都支持与字符串之间的转换;
  3. 原始对象的引用未保留。

需要在集合中存储原始类型的对象的需求促使我编写 NameValueCollection<T> 的泛型形式,作为 NameValueCollection 的替代方案。

背景

NameValueCollection<T> 集合基于 NameObjectCollectionBase - 关联的 string 键和 object 值的集合的基类,它包含访问这些值的基础方法。 IDictionaryIEnumerable<KeyValuePair<string,T>> 接口在类中实现,以提高可用性。

类定义

首先,定义包含键和值的类及其成员。 私有字段将在指定的数组中包含缓存的数据。 InvalidateCachedArrays 方法将重置缓存,并在每次数据更改时调用。

public partial class NameValueCollection<T> : NameObjectCollectionBase
{
    private string[] _keys; // Cached keys.
    private T[] _values;    // Cached values.

    // Resets the caches.
    protected void InvalidateCachedArrays()
    {
        _values = null;
        _keys = null;
    }
}

基本方法

下一步,是时候添加可以使用基类方法在集合中获取、设置和删除数据的类方法了。

AddSet 方法将接收到的值放入与指定键配对的列表中。

public partial class NameValueCollection<T>
{
    // Adds single value to collection.
    public void Add(string name, T value)
    {
        InvalidateCachedArrays();
        List<T> list = BaseGet(name) as List<T>;
        if (list == null)
        {
            list = new List<T>(1) {value};
            BaseAdd(name, list);
        }
        else
        {
            if (value == null) return;
            list.Add(value);
        }
    }

    // Adds range of values to collection.
    public void Add(NameValueCollection<T> collection)
    {
        InvalidateCachedArrays();
        int count = collection.Count;
        for (int i = 0; i < count; i++)
        {
            string key = collection.GetKey(i);
            T[] values = collection.Get(i);
            foreach (var value in values)
            {
                Add(key, value);
            }
        }
    }

    // Set single value (previous values will be removed).
    public void Set(string name, T value)
    {
        InvalidateCachedArrays();
        BaseSet(name, new List<T>(1){value});
    }

    // Set range of values (previous values will be removed).
    public void Set(string name, params T[] values)
    {
        InvalidateCachedArrays();
        BaseSet(name, new List<T>(values));
    }
}

GetKeyGet 方法返回请求的键和与键关联的值的数组。 GetAllValues 返回所有值,无论它们与哪些键配对,此方法在将来会很有用。

public partial class NameValueCollection<T>
{
    // Gets all values cache.
    protected T[] GetAllValues()
    {
        int count = Count;
        List<T> list = new List<T>(count);
        for (int i = 0; i < count; ++i)
        {
            list.AddRange(Get(i));
        }
        return list.ToArray();
    }

    // Gets all values that paired with specified key.
    public T[] Get(string name)
    {
        return ((List<T>)BaseGet(name)).ToArray();
    }

    // Gets all values at the specified index of collection.
    public T[] Get(int index)
    {
        return ((List<T>)BaseGet(index)).ToArray();
    }

    // Gets string containing the key at the specified index.
    public string GetKey(int index)
    {
        return BaseGetKey(index);
    }
}

ClearRemove 方法从集合中删除值。

public partial class NameValueCollection<T>
{
    // Removes values from the specified key.
    public void Remove(string name)
    {
        InvalidateCachedArrays();
        BaseRemove(name);
    }

    // Removes all data from the collection.
    public void Clear()
    {
        InvalidateCachedArrays();
        BaseClear();
    }
}

属性

快完成了! 为了使这个集合更易于使用,最好添加属性。 KeysValues 属性尝试返回缓存的数据,如果缓存失效,则更新它。

public partial class NameValueCollection<T>
{
    // All keys that the current collection contains.
    public string[] Keys
    {
        get
        {
            if (_keys == null)
                _keys = BaseGetAllKeys();
            return _keys;
        }
    }

    // All values that the current collection contains.
    public T[] Values
    {
        get
        {
            if (_values == null)
                _values = GetAllValues();
            return _values;
        }
    }

    // Values at the specified index.
    public T[] this[int index]
    {
        get
        {
            return Get(index);
        }
        set
        {
            BaseSet(index, new List<T>(value));
        }
    }

    // Values at the specified key.
    public T[] this[string name]
    {
        get
        {
            return Get(name);
        }
        set
        {
            BaseSet(name, new List<T>(value));
        }
    }
}

集合枚举

嵌入式类 Enumerator 将负责枚举集合中的所有键值对。 GetAllEntries 方法返回枚举器使用的所有键值对。

public partial class NameValueCollection<T>
{
    // Enumerates all entries.
    protected IEnumerable<KeyValuePair<string, T>> GetAllEntries()
    {
        return
            from key in Keys
            from value in Get(key)
            select new KeyValuePair<string, T>(key, value);
    }

    // The enumerator that can enumerate all entries in the collection.

    private class Enumerator : IEnumerator<KeyValuePair<string, T>>, IDictionaryEnumerator
    {
        // Enumerate all entries in collection.
        private readonly IEnumerator<KeyValuePair<string, T>> _enumerator;

        // Initialize the enumerator.
        public Enumerator(NameValueCollection<T> collection)
        {
            IEnumerable<KeyValuePair<string, T>> entries =
                collection.GetAllEntries().ToArray();
            _enumerator = entries.GetEnumerator();
        }

        // Returns the element of the collection corresponding
        // to the current position of the enumerator.
        KeyValuePair<string, T> IEnumerator<KeyValuePair<string, T>>.Current
        {
            get { return _enumerator.Current; }
        }

        // Returns the object of the collection corresponding
        // to the current position of the enumerator.
        object IEnumerator.Current
        {
            get
            {
                IEnumerator enumerator = ((IEnumerator) _enumerator);
                return enumerator.Current;
            }
        }

        // Gets the key of the current dictionary entry.
        object IDictionaryEnumerator.Key
        {
            get
            {
                IEnumerator<KeyValuePair<string, T>> enumerator =
                    ((IEnumerator<KeyValuePair<string, T>>) this);
                return enumerator.Current.Key;
            }
        }

        // Gets the value of the current dictionary entry.
        object IDictionaryEnumerator.Value
        {
            get
            {
                IEnumerator<KeyValuePair<string, T>> enumerator =
                    ((IEnumerator<KeyValuePair<string, T>>) this);
                return enumerator.Current.Value;
            }
        }

        // Gets both the key and the value of the current dictionary entry.
        DictionaryEntry IDictionaryEnumerator.Entry
        {
            get
            {
                IEnumerator<KeyValuePair<string, T>> enumerator =
                    ((IEnumerator<KeyValuePair<string, T>>) this);
                return new DictionaryEntry(enumerator.Current.Key,
                    enumerator.Current.Value);
            }
        }

        // Move enumerator to the next entry.
        public bool MoveNext()
        {
            return _enumerator.MoveNext();
        }

        // Reset enumerator to start position.
        public void Reset()
        {
            _enumerator.Reset();
        }

        // Unused dispose pattern.
        public void Dispose()
        {
        }
    }

}

最后一步是实现指定的接口 IDictionaryIEnumerable<KeyValuePair<string,T>>。 某些方法和属性是显式实现的,也就是说,它们的使用需要先强制转换为适当的接口类型。

public partial class NameValueCollection<T> :
  IDictionary,
  IEnumerable<KeyValuePair<string, T>>
{
    // Gets an collection containing the values.
    ICollection IDictionary.Keys => Keys;

    // Gets an collection containing the values.
    ICollection IDictionary.Values => Values;

    // Indicates whether the collection is read-only.
    public new bool IsReadOnly => base.IsReadOnly;

    // Indicates whether the collection contains a fixed number of items.
    public bool IsFixedSize => false;

    // Determines whether the collection contains an element with the specified key.
    bool IDictionary.Contains(object key)
    {
        return key is string s && Keys.Contains(s);
    }

    // Adds an object with the specified key to the colletion.
    void IDictionary.Add(object key, object value)
    {
        Add((string)key, (T)value);
    }

    // Removes the element with the specified key from the collection.
    void IDictionary.Remove(object key)
    {
       Remove((string)key);
    }

    // Gets or sets the item with the specified key.
    object IDictionary.this[object key]
    {
        get
        {
            return Get((string)key);
        }
        set
        {
            if (value is IEnumerable<T> collection)
            {
                Set((string)key, (T[])collection.ToArray());
            }
            else
            {
                Set((string)key, (T)value);
            }
        }
    }

    // The three methods below return an enumerator for current collection.
    IEnumerator<KeyValuePair<string, T>> IEnumerable<KeyValuePair<string, T>>.GetEnumerator()
    {
        return new Enumerator(this);
    }

    public override IEnumerator GetEnumerator()
    {
        return new Enumerator(this);
    }

    IDictionaryEnumerator IDictionary.GetEnumerator()
    {
        return new Enumerator(this);
    }
}

用法

NameValueCollection<int> collection = new NameValueCollection<int>();
collection.Add("a", 123);
collection.Add("a", 456);      // 123 and 456 will be inserted into the same key.
collection.Add("b", 789);      // 789 will be inserted into another key.

int[] a = collection.Get("a"); // contains 123 and 456.
int[] b = collection.Get("b"); // contains 789.

在本文的结尾,我想说上面的代码实现了基本功能。 在附件中,您将找到包含本文未包含的一些其他扩展的完整源代码。

↑ 返回目录。

历史

  • 2022年1月26日:初始版本
  • 2022年2月5日:ArrayList 已被 List 替换。
© . All rights reserved.