In this article, you will see the details about NameValueCollection - a generic collection of associated string keys and given type values that can be accessed either with the key or with the index.
- Introduction
- Background
- Usage
- History
Built into mscorlib realization of NameValueCollection is a collection that is similar to a Dictionary<string,string> but NameValueCollection can have duplicate keys while Dictionary<string,string> cannot. Elements can be obtained both by index and by key.
What makes this collection special, is that one key can contain several elements and that null is allowed as a key or as a value. But there is one small problem. NameValueCollection assumes that strings are used as both keys and values. So what if you want to store values of any type, not only string? Of course, you can convert the text to the desired type every time you get a value, but there are three significant limitations here:
- processing overhead for conversion;
- not all types support conversion to and from a string;
- references to the original objects are not preserved.
The need to store objects in a collection in the original type attracted me to write a generic form of NameValueCollection<T> as an alternative to NameValueCollection.
The NameValueCollection<T> collection is based on NameObjectCollectionBase - the base class for a collection of associated string keys and object values that contains base methods to access the values. The interfaces IDictionary, IEnumerable<KeyValuePair<string,T>> were implemented in the class as additional usability.
For the first time, define the class and its members that contain keys and values. Private fields will contain the cached data in specified arrays. The InvalidateCachedArrays
method will reset the caches and will be called every time the data changes.
public partial class NameValueCollection<T> : NameObjectCollectionBase
{
private string[] _keys;
private T[] _values;
protected void InvalidateCachedArrays()
{
_values = null;
_keys = null;
}
}
The next step, it's time to add in the class methods that can get, set and remove data in collection using base class methods.
The Add
and Set
methods put received values into the list that paired with the specified key.
public partial class NameValueCollection<T>
{
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);
}
}
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);
}
}
}
public void Set(string name, T value)
{
InvalidateCachedArrays();
BaseSet(name, new List<T>(1){value});
}
public void Set(string name, params T[] values)
{
InvalidateCachedArrays();
BaseSet(name, new List<T>(values));
}
}
The GetKey
and Get
methods return the requested key and array of associated with key values. The GetAllValues
returns all values regardless of keys they are paired with, these method will be useful in the future.
public partial class NameValueCollection<T>
{
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();
}
public T[] Get(string name)
{
return ((List<T>)BaseGet(name)).ToArray();
}
public T[] Get(int index)
{
return ((List<T>)BaseGet(index)).ToArray();
}
public string GetKey(int index)
{
return BaseGetKey(index);
}
}
The Clear
and Remove
methods delete values from the collection.
public partial class NameValueCollection<T>
{
public void Remove(string name)
{
InvalidateCachedArrays();
BaseRemove(name);
}
public void Clear()
{
InvalidateCachedArrays();
BaseClear();
}
}
Almost done! To make this collection easier to use, it's a good idea to add properties. The Keys
and Values
properties attempt to return cached data and update it if the cache is invalidated.
public partial class NameValueCollection<T>
{
public string[] Keys
{
get
{
if (_keys == null)
_keys = BaseGetAllKeys();
return _keys;
}
}
public T[] Values
{
get
{
if (_values == null)
_values = GetAllValues();
return _values;
}
}
public T[] this[int index]
{
get
{
return Get(index);
}
set
{
BaseSet(index, new List<T>(value));
}
}
public T[] this[string name]
{
get
{
return Get(name);
}
set
{
BaseSet(name, new List<T>(value));
}
}
}
The embedded class Enumerator
will be responsible for enumerating all key-values pairs in the collection. The GetAllEntries
method return all key-values pairs that are used by the enumerator.
public partial class NameValueCollection<T>
{
protected IEnumerable<KeyValuePair<string, T>> GetAllEntries()
{
return
from key in Keys
from value in Get(key)
select new KeyValuePair<string, T>(key, value);
}
private class Enumerator : IEnumerator<KeyValuePair<string, T>>, IDictionaryEnumerator
{
private readonly IEnumerator<KeyValuePair<string, T>> _enumerator;
public Enumerator(NameValueCollection<T> collection)
{
IEnumerable<KeyValuePair<string, T>> entries =
collection.GetAllEntries().ToArray();
_enumerator = entries.GetEnumerator();
}
KeyValuePair<string, T> IEnumerator<KeyValuePair<string, T>>.Current
{
get { return _enumerator.Current; }
}
object IEnumerator.Current
{
get
{
IEnumerator enumerator = ((IEnumerator) _enumerator);
return enumerator.Current;
}
}
object IDictionaryEnumerator.Key
{
get
{
IEnumerator<KeyValuePair<string, T>> enumerator =
((IEnumerator<KeyValuePair<string, T>>) this);
return enumerator.Current.Key;
}
}
object IDictionaryEnumerator.Value
{
get
{
IEnumerator<KeyValuePair<string, T>> enumerator =
((IEnumerator<KeyValuePair<string, T>>) this);
return enumerator.Current.Value;
}
}
DictionaryEntry IDictionaryEnumerator.Entry
{
get
{
IEnumerator<KeyValuePair<string, T>> enumerator =
((IEnumerator<KeyValuePair<string, T>>) this);
return new DictionaryEntry(enumerator.Current.Key,
enumerator.Current.Value);
}
}
public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset()
{
_enumerator.Reset();
}
public void Dispose()
{
}
}
}
The last step is to implement the specified interfaces IDictionary and IEnumerable<KeyValuePair<string,T>>. Some methods and properties are implemented explicitly, that is, their use requires a prior casting to the appropriate interface's type.
public partial class NameValueCollection<T> :
IDictionary,
IEnumerable<KeyValuePair<string, T>>
{
ICollection IDictionary.Keys => Keys;
ICollection IDictionary.Values => Values;
public new bool IsReadOnly => base.IsReadOnly;
public bool IsFixedSize => false;
bool IDictionary.Contains(object key)
{
return key is string s && Keys.Contains(s);
}
void IDictionary.Add(object key, object value)
{
Add((string)key, (T)value);
}
void IDictionary.Remove(object key)
{
Remove((string)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);
}
}
}
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);
collection.Add("b", 789);
int[] a = collection.Get("a");
int[] b = collection.Get("b");
At the end of this article, I would like to tell that the above code implements the basic features. In the attached file, you will find the full source code containing some additional extensions that are not included in the article.
↑ Back to contents.
- 26th January, 2022: Initial version
- 05th February, 2022: ArrayList has been replaced by List.