Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

1 to 1 Object Mapping with IDictionary

4.50/5 (2 votes)
15 Nov 2010CPOL 13.9K  
All too often, I have to match disparate objects 1 to 1
This is a drop-in solution to a problem that I often find myself facing: a situation where two programmers have defined the same concept in two different ways, and I am forced to translate between them. Obviously, a refactor is better, but sometimes you're simply not given the time needed to do so. This is also particularly helpful in mapping between and Enum value and a displayable string (Yes, you can use enum.toString() as long as you don't want punctuation, or spaces, and your front-end guys and back-end guys agree on what to call something)

First, the object:

XML
/// <summary>
/// This is a dictionary guaranteed to have only one of each value and key.
/// It may be searched either by TFirst or by TSecond, giving a unique answer because it is 1 to 1.
/// In any IDictionary methods that take in Key/Value pairs, Key is TFirst and Value is TSecond
///
/// This class violates the injection/surjection contract for the idictionary interface as a low-risk implementation detail
/// </summary>
/// <typeparam name="TFirst">The type of the "key"</typeparam>
/// <typeparam name="TSecond">The type of the "value"</typeparam>
public class BijectiveBidirectionalMap<TFirst, TSecond> : IDictionary<TFirst, TSecond>
{
    IDictionary<TFirst, TSecond> firstToSecond = new Dictionary<TFirst, TSecond>();
    IDictionary<TSecond, TFirst> secondToFirst = new Dictionary<TSecond, TFirst>();
    public BijectiveBidirectionalMap()
    {
        if (typeof(TFirst) == typeof(TSecond)) throw new ArgumentException("Cannot map between identical types.");
    }
    #region Exception throwing methods
    public bool ContainsKey(TFirst key)
    {
        return firstToSecond.ContainsKey(key);
    }
    public bool ContainsKey(TSecond key)
    {
        return secondToFirst.ContainsKey(key);
    }
    /// <summary>
    /// Tries to add the pair to the dictionary.
    /// Throws an exception if either element is already in the dictionary
    /// </summary>
    /// <param name="first"></param>
    /// <param name="second"></param>
    public void Add(TFirst first, TSecond second)
    {
        if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
            throw new ArgumentException("Duplicate first or second");
        firstToSecond.Add(first, second);
        secondToFirst.Add(second, first);
    }
    public bool Remove(TFirst key)
    {
        return TryRemoveByFirst(key);
    }
    public bool Remove(TSecond key)
    {
        return TryRemoveBySecond(key);
    }
    public bool TryGetValue(TFirst key, out TSecond value)
    {
        return TryGetByFirst(key, out value);
    }
    public bool TryGetValue(TSecond key, out TFirst value)
    {
        return TryGetBySecond(key, out value);
    }
    public TSecond this[TFirst key]
    {
        get { return GetByFirst(key); }
        set { Add(key, value); }
    }
    public TFirst this[TSecond key]
    {
        get { return GetBySecond(key); }
        set { Add(value, key); }
    }
    public ICollection<TFirst> Keys
    {
        get { return firstToSecond.Keys; }
    }
    public ICollection<TSecond> Values
    {
        get { return firstToSecond.Values; }
    }
    /// <summary>
    /// Find the TSecond corresponding to the TFirst first
    /// Throws an exception if first is not in the dictionary.
    /// </summary>
    /// <param name="first">the key to search for</param>
    /// <returns>the value corresponding to first</returns>
    private TSecond GetByFirst(TFirst first)
    {
        TSecond second;
        if (!firstToSecond.TryGetValue(first, out second))
            throw new ArgumentException("first");
        return second;
    }
    /// <summary>
    /// Find the TFirst corresponing to the Second second.
    /// Throws an exception if second is not in the dictionary.
    /// </summary>
    /// <param name="second">the key to search for</param>
    /// <returns>the value corresponding to second</returns>
    private TFirst GetBySecond(TSecond second)
    {
        TFirst first;
        if (!secondToFirst.TryGetValue(second, out first))
            throw new ArgumentException("second");
        return first;
    }

    /// <summary>
    /// Remove the record containing first.
    /// If first is not in the dictionary, throws an Exception.
    /// </summary>
    /// <param name="first">the key of the record to delete</param>
    private void RemoveByFirst(TFirst first)
    {
        TSecond second;
        if (!firstToSecond.TryGetValue(first, out second))
            throw new ArgumentException("first");
        firstToSecond.Remove(first);
        secondToFirst.Remove(second);
    }
    /// <summary>
    /// Remove the record containing second.
    /// If second is not in the dictionary, throws an Exception.
    /// </summary>
    /// <param name="second">the key of the record to delete</param>
    private void RemoveBySecond(TSecond second)
    {
        TFirst first;
        if (!secondToFirst.TryGetValue(second, out first))
            throw new ArgumentException("second");
        secondToFirst.Remove(second);
        firstToSecond.Remove(first);
    }
    #endregion
    #region Try methods
    /// <summary>
    /// Tries to add the pair to the dictionary.
    /// Returns false if either element is already in the dictionary
    /// </summary>
    /// <param name="first"></param>
    /// <param name="second"></param>
    /// <returns>true if successfully added, false if either element are already in the dictionary</returns>
    private Boolean TryAdd(TFirst first, TSecond second)
    {
        if (firstToSecond.ContainsKey(first) || secondToFirst.ContainsKey(second))
            return false;
        firstToSecond.Add(first, second);
        secondToFirst.Add(second, first);
        return true;
    }

    /// <summary>
    /// Find the TSecond corresponding to the TFirst first.
    /// Returns false if first is not in the dictionary.
    /// </summary>
    /// <param name="first">the key to search for</param>
    /// <param name="second">the corresponding value</param>
    /// <returns>true if first is in the dictionary, false otherwise</returns>
    private Boolean TryGetByFirst(TFirst first, out TSecond second)
    {
        return firstToSecond.TryGetValue(first, out second);
    }
    /// <summary>
    /// Find the TFirst corresponding to the TSecond second.
    /// Returns false if second is not in the dictionary.
    /// </summary>
    /// <param name="second">the key to search for</param>
    /// <param name="first">the corresponding value</param>
    /// <returns>true if second is in the dictionary, false otherwise</returns>
    private Boolean TryGetBySecond(TSecond second, out TFirst first)
    {
        return secondToFirst.TryGetValue(second, out first);
    }
    /// <summary>
    /// Remove the record containing first, if there is one.
    /// </summary>
    /// <param name="first"></param>
    /// <returns> If first is not in the dictionary, returns false, otherwise true</returns>
    private Boolean TryRemoveByFirst(TFirst first)
    {
        TSecond second;
        if (!firstToSecond.TryGetValue(first, out second))
            return false;
        firstToSecond.Remove(first);
        secondToFirst.Remove(second);
        return true;
    }
    /// <summary>
    /// Remove the record containing second, if there is one.
    /// </summary>
    /// <param name="second"></param>
    /// <returns> If second is not in the dictionary, returns false, otherwise true</returns>
    private Boolean TryRemoveBySecond(TSecond second)
    {
        TFirst first;
        if (!secondToFirst.TryGetValue(second, out first))
            return false;
        secondToFirst.Remove(second);
        firstToSecond.Remove(first);
        return true;
    }
    #endregion
    public bool Remove(KeyValuePair<TFirst, TSecond> item)
    {
        bool success = false;
        if (firstToSecond.Contains(item))
        {
            success = firstToSecond.Remove(item.Key);
            if (success) success = secondToFirst.Remove(item.Value);
        }
        return success;
    }
    /// <summary>
    /// The number of pairs stored in the dictionary
    /// </summary>
    public Int32 Count
    {
        get { return firstToSecond.Count; }
    }
    public bool IsReadOnly
    {
        get { return firstToSecond.IsReadOnly; }
    }
    public void Add(KeyValuePair<TFirst, TSecond> item)
    {
        Add(item.Key, item.Value);
    }
    /// <summary>
    /// Removes all items from the dictionary.
    /// </summary>
    public void Clear()
    {
        firstToSecond.Clear();
        secondToFirst.Clear();
    }
    public bool Contains(KeyValuePair<TFirst, TSecond> item)
    {
        return (firstToSecond.Contains(item));
    }
    public void CopyTo(KeyValuePair<TFirst, TSecond>[] array, int arrayIndex)
    {
        CopyTo(array, arrayIndex);
    }
    public IEnumerator<KeyValuePair<TFirst, TSecond>> GetEnumerator()
    {
        return firstToSecond.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}


And second, a rediculously simplified usage:
public enum AwesomeEnum
{
    Hello,
    World
}
private void demo()
{
    BijectiveBidirectionalMap mapping =
        new BijectiveBidirectionalMap();

    mapping.Add(AwesomeEnum.Hello, "Hello,");
    mapping.Add(AwesomeEnum.World, "World!");

    AwesomeEnum ae = mapping["Hello,"];
    string s = mapping[AwesomeEnum.World];

}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)