Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#
Tip/Trick

1 to 1 Object Mapping with IDictionary

Rate me:
Please Sign up or sign in to vote.
4.50/5 (2 votes)
15 Nov 2010CPOL 13.5K   2   2
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)


Written By
Software Developer (Senior) InGage Networks
United States United States
James is currently a Software developer after a detour from that profession into IT because he 'didn't want to sit in a cubicle and wanted to deal with people.' He has since learned that people are stupid, and enjoys his cubicle greatly.

Comments and Discussions

 
GeneralI actually agree with you, Emile. A friend of mine and I sp... Pin
qlipoth24-Nov-10 3:16
qlipoth24-Nov-10 3:16 
GeneralReason for my vote of 5 Neat! I would probably call the thin... Pin
Emile van Gerwen23-Nov-10 21:14
Emile van Gerwen23-Nov-10 21:14 
Reason for my vote of 5
Neat! I would probably call the thing UniqueMap or OneOnOneMap. Not as exact as BijectiveBidirectionalMap but reads better in my opinion.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.