Home > Development, PT > Multi Value Dictionary Objects

Multi Value Dictionary Objects

3 03UTC August 03UTC 2009 Sérgio Charrua Leave a comment Go to comments

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

Categories: Development, PT Tags:
  1. 3 03UTC August 03UTC 2009 at 17:46 | #1

    Sandro,

    Muito interessante.

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

    Duarte

  1. No trackbacks yet.