Skip to content

Instantly share code, notes, and snippets.

@atifaziz
Created October 29, 2008 09:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atifaziz/20664 to your computer and use it in GitHub Desktop.
Save atifaziz/20664 to your computer and use it in GitHub Desktop.
A modern implementation of NameValueCollection
#region Imports
using System;
using System.Diagnostics;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
#endregion
public abstract class NameObjectCollectionBase : ICollection
{
private Dictionary<string, int> _indexByKey;
private List<Entry> _entries;
private int? _nullKeyIndex;
private readonly int _initialCapacity;
private KeysCollection _keys;
[ DebuggerDisplay("{ Key = {Key}, Value = {Value} }") ]
private struct Entry
{
public readonly string Key;
public readonly object Value;
public Entry(string key, object value)
{
Debug.Assert(key != null);
Key = key;
Value = value;
}
}
internal IEqualityComparer<string> EqualityComparer { get; private set; }
protected NameObjectCollectionBase() :
this(0) {}
protected NameObjectCollectionBase(int capacity) :
this(capacity, null) {}
protected NameObjectCollectionBase (IEqualityComparer<string> equalityComparer) :
this(0, equalityComparer) {}
protected NameObjectCollectionBase(int capacity, IEqualityComparer<string> equalityComparer)
{
EqualityComparer = equalityComparer ?? StringComparer.InvariantCultureIgnoreCase;
_initialCapacity = capacity;
Reset();
}
private void Reset()
{
_indexByKey = new Dictionary<string, int>(_initialCapacity, EqualityComparer);
_entries = new List<Entry>(_initialCapacity);
_nullKeyIndex = null;
}
public virtual KeysCollection Keys
{
get
{
if (_keys == null)
_keys = new KeysCollection(this);
return _keys;
}
}
public virtual IEnumerator GetEnumerator()
{
return Keys.GetEnumerator();
}
public virtual int Count
{
get { return _entries.Count; }
}
bool ICollection.IsSynchronized
{
get { return false; }
}
object ICollection.SyncRoot
{
get { return this; }
}
void ICollection.CopyTo(Array array, int index)
{
((ICollection) Keys).CopyTo(array, index);
}
protected bool IsReadOnly { get; set; }
protected void BaseAdd(string name, object value)
{
RequireWriteAccess();
var entry = new Entry(name, value);
var index = _entries.Count;
if (name == null)
{
if (_nullKeyIndex == null)
_nullKeyIndex = index;
}
else
{
int unused;
if (!_indexByKey.TryGetValue(name, out unused))
_indexByKey.Add(name, index);
}
_entries.Add(entry);
}
protected void BaseClear()
{
RequireWriteAccess();
Reset();
}
protected object BaseGet(int index)
{
return _entries[index].Value;
}
protected object BaseGet(string name)
{
var index = NameToIndex(name);
return index == null ? null : _entries[index.Value].Value;
}
protected string[] BaseGetAllKeys()
{
return _entries.Select(e => e.Key).ToArray();
}
protected object[] BaseGetAllValues()
{
return _entries.Select((e, i) => BaseGet(i)).ToArray();
}
protected object[] BaseGetAllValues(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
var count = _entries.Count;
var values = (object[]) Array.CreateInstance(type, count);
for (var i = 0; i < count; i++)
values[i] = BaseGet(i);
return values;
}
protected string BaseGetKey(int index)
{
return _entries[index].Key;
}
protected bool BaseHasKeys()
{
return _indexByKey.Count > 0;
}
protected void BaseRemove(string name)
{
RequireWriteAccess();
if (name != null)
_indexByKey.Remove(name);
else
_nullKeyIndex = null;
var count = _entries.Count;
for (var i = 0; i < count; )
{
var key = BaseGetKey(i);
if (EqualityComparer.Equals(key, name))
{
_entries.RemoveAt(i);
count--;
}
else
{
i++;
}
}
}
protected void BaseRemoveAt(int index)
{
RequireWriteAccess();
var key = BaseGetKey(index);
if (key != null)
_indexByKey.Remove(key);
else
_nullKeyIndex = null;
_entries.RemoveAt(index);
}
protected void BaseSet(int index, object value)
{
RequireWriteAccess();
var current = _entries[index];
_entries[index] = new Entry(current.Key, value);
}
protected void BaseSet(string name, object value)
{
RequireWriteAccess();
var index = NameToIndex(name);
if (index != null)
_entries[index.Value] = new Entry(name, value);
else
BaseAdd(name, value);
}
protected void RequireWriteAccess()
{
if (IsReadOnly)
throw new NotSupportedException("Collection is read-only.");
}
private int? NameToIndex(string name)
{
int index;
return name == null
? _nullKeyIndex
: _indexByKey.TryGetValue(name, out index)
? index
: (int?) null;
}
public class KeysCollection : ICollection
{
private readonly NameObjectCollectionBase _collection;
internal KeysCollection(NameObjectCollectionBase collection)
{
_collection = collection;
}
public string this[int index] { get { return Get(index); } }
public int Count { get { return _collection.Count; } }
bool ICollection.IsSynchronized { get { return false; } }
object ICollection.SyncRoot { get { return _collection; } }
public virtual string Get(int index)
{
return _collection.BaseGetKey(index);
}
public IEnumerator GetEnumerator()
{
foreach (var item in _collection._entries)
yield return item.Key;
}
void ICollection.CopyTo(Array array, int arrayIndex)
{
_collection._entries.Select(e => e.Key).ToArray().CopyTo(array, arrayIndex);
}
}
}
#region Imports
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
#endregion
public class NameValueCollection : NameObjectCollectionBase
{
private string[] _cachedKeys;
private string[] _cachedValues;
public NameValueCollection() { }
public NameValueCollection(int capacity) :
base(capacity) { }
public NameValueCollection(NameValueCollection col) :
base(col == null ? null : col.EqualityComparer)
{
if (col == null) throw new ArgumentNullException("col");
Add(col);
}
public NameValueCollection(int capacity, NameValueCollection col) :
base(capacity, col == null ? null : col.EqualityComparer)
{
Add(col);
}
public NameValueCollection(IEqualityComparer<string> equalityComparer) :
base(equalityComparer) { }
public NameValueCollection(int capacity, IEqualityComparer<string> equalityComparer) :
base(capacity, equalityComparer) { }
public virtual string[] AllKeys
{
get
{
if (_cachedKeys == null)
_cachedKeys = BaseGetAllKeys();
return _cachedKeys;
}
}
public string this[int index] { get { return Get(index); } }
public string this[string name]
{
get { return Get(name); }
set { Set(name, value); }
}
private List<string> GetStringList(string name)
{
return (List<string>) BaseGet(name);
}
private List<string> GetStringList(int index)
{
return (List<string>) BaseGet(index);
}
public void Add(NameValueCollection c)
{
if (c == null) throw new ArgumentNullException("c");
RequireWriteAccess();
InvalidateCachedArrays();
var count = c.Count;
for (var i = 0; i < count; i++)
{
var key = c.GetKey(i);
var those = (IEnumerable<string>) c.BaseGet(i);
var these = (List<string>) BaseGet(key);
if (these != null && those != null)
these.AddRange(those);
else if (those != null)
these = new List<string>(those);
BaseSet(key, these);
}
}
public virtual void Add(string name, string val)
{
RequireWriteAccess();
InvalidateCachedArrays();
var values = GetStringList(name);
if (values == null)
{
values = new List<string>();
if (val != null)
values.Add(val);
BaseAdd(name, values);
}
else
{
if (val != null)
values.Add(val);
}
}
public virtual void Clear()
{
RequireWriteAccess();
InvalidateCachedArrays();
BaseClear();
}
public void CopyTo(Array dest, int index)
{
if (dest == null) throw new ArgumentNullException("dest");
if (_cachedValues == null)
_cachedValues = Enumerable.Range(0, Count).Select(i => Get(i)).ToArray();
_cachedValues.CopyTo(dest, index);
}
public virtual string Get(int index)
{
return ToDelimitedString(GetStringList(index));
}
public virtual string Get(string name)
{
return ToDelimitedString(GetStringList(name));
}
public virtual string GetKey(int index)
{
return BaseGetKey(index);
}
public virtual string[] GetValues(int index)
{
return ToStringArray(GetStringList(index));
}
public virtual string[] GetValues(string name)
{
return ToStringArray(GetStringList(name));
}
public bool HasKeys()
{
return BaseHasKeys();
}
public virtual void Remove(string name)
{
RequireWriteAccess();
InvalidateCachedArrays();
BaseRemove(name);
}
public virtual void Set(string name, string value)
{
RequireWriteAccess();
InvalidateCachedArrays();
var values = new List<string>();
if (value != null)
{
values.Add(value);
BaseSet(name, values);
}
else
{
BaseSet(name, null);
}
}
protected void InvalidateCachedArrays()
{
_cachedKeys = null;
_cachedValues = null;
}
private static string ToDelimitedString(ICollection<string> values)
{
if (values == null || values.Count == 0)
return null;
using (var e = values.GetEnumerator())
{
e.MoveNext();
var sb = new StringBuilder(e.Current);
while (e.MoveNext())
sb.Append(',').Append(e.Current);
return sb.ToString();
}
}
private static string[] ToStringArray(ICollection<string> values)
{
return values == null || values.Count == 0 ? null : values.ToArray();
}
}
@amkherad
Copy link

amkherad commented Mar 6, 2017

Thank you so much, you saved my day!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment