Multi Value Dictionary Objects
Por vezes uma colecção de objectos, implementando a interface ICollection, não é suficiente. Refiro-me por exemplo à necessidade de agrupar uma colecção de objectos a uma determinada chave, algo que só o Dictionary consegue fazer … mas pouco!
Enquanto que se fizermos algo como :
Object obj = new Object; MyObjectCollection objCOL = new ObjectCollection(); objCOL.Add(obj);
conseguimos obter uma lista de objectos numa mesma colecção, se tivermos dezenas de colecções e pretendermos devolver a colecção X, não é trivial.
O Dictionary até pode vir ajudar:
Dictionary<string, object> dict = null; dict = new Dictionary<string,object>(); dict.Add(key, obj);
e ficamos assim com um objecto armazenado numa posição nomeada key, no Dictionary. Mas aí está: não é uma colecção de objectos numa mesma posição do Dictionary.
Ok. Poderíamos fazer algo como:
MyObjectCollection objCOL = new ObjectCollection();
objCOL.Add(obj);
dict.Add(key, objCOL);
e aí teríamos uma colecção numa dada posição do Dictionary.
Pessoalmente, prefiro algo como
MultiValueDictionary mvd = new MultiValueDictionary();
mvd.Add(key, obj);
mvd.Add(key, obj);
mvd.add(key, objCOL);
…. And so on….
Bastante mais simples, e mais prático. Até porque conseguimos enumerar o Dictionary todo, e devolver os objectos todos de cada uma das chaves/posição do Dictionary.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Collections; /// <summary> /// A class that extends the normal Dictionary object. Can store more than one value for a given key, keeping a list for every key value. /// When Adding a Value to the same Key, the value will be stored under the same key. Fetching values for a Key will return a List of Values /// </summary> /// <typeparam name="Key">The type of the key.</typeparam> /// <typeparam name="Value">The type of the value.</typeparam> public class MultiValueDictionary<Key, Value> : Dictionary<Key, List<Value>>, ILookup<Key, Value> { /// <summary> /// Initializes a new instance . /// </summary> public MultiValueDictionary() : base() { } /// <summary> /// Adds the specified value under the specified key /// </summary> /// <param name="key">key</param> /// <param name="value">value</param> public void Add(Key key, Value value) { List<Value> container; if (!this.TryGetValue(key, out container)) { container = new List<Value>(); this.Add(key, container); } container.Add(value); } /// <summary> /// Adds the range of values under the key specified. /// </summary> /// <param name="key">key</param> /// <param name="values">values</param> public void AddRange(Key key, IEnumerable<Value> values) { if (values == null) { return; } foreach (Value value in values) { this.Add(key, value); } } /// <summary> /// Determines whether this dictionary contains the specified value for the specified key /// </summary> /// <param name="key">key</param> /// <param name="value">value</param> /// <returns>true if the value is stored for the specified key in this dictionary, false otherwise</returns> public bool ContainsValue(Key key, Value value) { bool toReturn = false; List<Value> values; if (this.TryGetValue(key, out values)) { toReturn = values.Contains(value); } return toReturn; } /// <summary> /// Removes the specified value for the specified key, leaving the key in the dictionary. /// </summary> /// <param name="key">key</param> /// <param name="value">value</param> public void Remove(Key key, Value value) { List<Value> container; if (this.TryGetValue(key, out container)) { container.Remove(value); if (container.Count <= 0) { this.Remove(key); } } } /// <summary> /// Merges the specified multivaluedictionary into this instance. /// </summary> /// <param name="toMergeWith">To merge with.</param> public void Merge(MultiValueDictionary<Key, Value> toMergeWith) { if (toMergeWith == null) { return; } foreach (KeyValuePair<Key, List<Value>> pair in toMergeWith) { foreach (Value value in pair.Value) { this.Add(pair.Key, value); } } } /// <summary> /// Gets the values for the key specified. /// This method is useful if you want to avoid an exception for key value retrieval and you can't use TryGetValue /// (in lambdas, for example) /// </summary> /// <param name="key">key</param> /// <param name="returnEmptySet">if set to true and the key isn't found, returns an empty hashset. If set to false, and the key isn't found, returns null</param> /// <returns> /// This method will return null (or an empty set if returnEmptySet is true) if the key wasn't found, or /// the values if key was found. /// </returns> public List<Value> GetValues(Key key, bool returnEmptySet) { List<Value> toReturn; if (!this.TryGetValue(key, out toReturn) && returnEmptySet) { toReturn = new List<Value>(); } return toReturn; } #region ILookup<TKey,TValue> Members /// <summary> /// Determines whether a specified key exists in the /// </summary> /// <param name="key">The key to search for /// <returns> /// true if key is founds /// </returns> bool ILookup<Key, Value>.Contains(Key key) { return this.ContainsKey(key); } /// <summary> /// Gets the number of key/value pairs contained in the MultiValueDictionary /// </summary> /// <value></value> /// <returns> /// The number of key/value pairs contained in the MultiValueDictionary /// </returns> int ILookup<Key, Value>.Count { get { return this.Count; } } /// <summary> /// Gets the value with the specified key. /// </summary> /// <value></value> IEnumerable<Value> ILookup<Key, Value>.this[Key key] { get { return this.GetValues(key, true); } } #endregion #region IEnumerable<IGrouping<TKey,TValue>> Members /// <summary> /// Returns an enumerator /// </summary> /// <returns> /// </returns> IEnumerator<IGrouping<Key, Value>> IEnumerable<IGrouping<Key, Value>>.GetEnumerator() { foreach (KeyValuePair<Key, List<Value>> pair in this) { yield return new Grouping<Key, Value>(pair.Key, pair.Value); } } #endregion #region IEnumerable Members /// <summary> /// Returns an enumerator /// </summary> /// <returns> /// </returns> IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion } /// <summary> /// Class which implements the IGrouping interface to return grouped results in a query /// </summary> /// <typeparam name="Key">type of the grouping key</typeparam> /// <typeparam name="Element">type of the elements grouped</typeparam> public class Grouping<Key, Element> : IGrouping<Key, Element> { #region Class Member Declarations private readonly Key _key; private readonly IEnumerable<Element> _elements; #endregion /// <summary> /// Constructor /// </summary> /// <param name="key">key</param> /// <param name="elements">grouped elements</param> public Grouping(Key key, IEnumerable<Element> elements) { _key = key; _elements = elements; } /// <summary> /// Gets the key /// </summary> /// <returns></returns> Key IGrouping<Key, Element>.Key { get { return _key; } } /// <summary> /// Returns an enumerator /// </summary> /// <returns> /// </returns> public IEnumerator<Element> GetEnumerator() { if (_elements == null) { return null; } return _elements.GetEnumerator(); } /// <summary> /// Returns an enumerator /// </summary> /// <returns> /// </returns> IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } }
Note: original code from http://weblogs.asp.net/fbouma

Sandro,
Muito interessante.
Artigos de programação em pt… e na esfera sig… é coisa que gostava de ver mais vezes na net.
Duarte