Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A generic Trictionary class

0.00/5 (No votes)
11 Mar 2009 1  
This article describes a generic Trictionary class derived from Dictionary that allows two values of different types per key

Introduction

I wrote this article and the accompanying code purely for fun. It's been a while since I wrote an article and I thought this would help me get into groove. The article was inspired by another article on the same topic :

That article describes a Trictionary class that is essentially a dictionary but for each key there are two values, both of differing types. In many cases instead of doing this, the proper approach would most likely be to use a struct that would have those two types as members. But there may also be scenarios where you may want to avoid having to unnecessarily create a struct just for this purpose. You could also use an anonymous type, but that's really not so different in the sense you still end up having a new type in your assembly. Joe Enos's Trictionary class interested me though I did not like the syntactic usage the class permitted. Here's some sample code from his article :

Trictionary<int, string, double> trict = new Trictionary<int, string, double>();

// ADDING:
// Option 1:
trict.Add(1, "A", 1.1);

// Option 2:
trict[2] = new DualObjectContainer<string,double>("B", 2.2);

// RETRIEVING:
// Option 1:
string s;
double d;
trict.Get(2, out s, out d);

// Option 2:
DualObjectContainer<string, double> container = trict[1];

I did not like the fact that you had to call an Add method or create an object just to add entries to the Trictionary. I also did not like the retrieval mechanism, calling a Get method with two out parameters is not very elegant, and getting back the sub-object he uses to store the values is even more messy in my opinion. That's why I quickly put together this little class. I haven't ever had to use it myself (well given that I just wrote it an hour or so ago, I didn't have the opportunity to do so) but I may use it some time in future.

Usage

 Here's some sample code that shows how my Trictionary can be used.

static void Main()
{
    Trictionary<int, string, double> trictionary = 
        new Trictionary<int, string, double>();

    trictionary[10] = 10.7;
    trictionary[10] = "sss";
    trictionary[10] = "sss-mod";

    string s = trictionary[10];
    double d = trictionary[10];

    Console.WriteLine(s);
    Console.WriteLine(d);

    trictionary[12] = 11.4;
    trictionary[12] = "bbb";
    trictionary[12] = 11.5;

    trictionary[15] = 10.1;

    trictionary[16] = "ppp";

    trictionary[19] = "bbb";
    trictionary[19] = 11.5;

    foreach (var value in trictionary.Values)
    {
        Console.WriteLine("{0}, {1}", (string)value, (double)value );
    }
}

Notice how the class can be used in a way that's a lot more intuitive to the developer assuming he's familiar with Dictionary usage. In the above sample, the two types associated with the values are string and double. You just assign string or double values via the indexer (as you'd normally do with the Dictionary class). Retrieval is a mere matter of casting to string or double. There may be folks out there who think Joe Enos's class provides a more intuitive usage structure - well, different people have different ideas on all these things. I am sure there'll be a few people who'll like my class usage better - so it really doesn't matter much.

Limitation

The two values cannot be of the same type. If that's the case then you don't need this class - you can just use List<T> or ICollection<T> as the value type of the regular Dictionary class. This is "by design". The following code will not compile.

Trictionary<int, string, string> trictionary2 = 
  new Trictionary<int, string, string>();
trictionary2[1] = "hello";
trictionary2[2] = "world"

Implementation details

My Trictionary class derives from Dictionary. Here's the full class listing (re-formatted for the browser width) :

/// <summary>
/// Represents a dictionary with two distinct typed values per key
/// </summary>
/// <typeparam name="TKey">The type of the dictionary key</typeparam>
/// <typeparam name="TValue1">The type of the first value</typeparam>
/// <typeparam name="TValue2">The type of the second value</typeparam>
[Serializable]
public class Trictionary<TKey, TValue1, TValue2> 
  : Dictionary<TKey, DualObject<TValue1, TValue2>>
{
    /// <summary>
    /// Initializes a new instance of the Trictionary class
    /// </summary>
    public Trictionary()
    {
    }

    /// <summary>
    /// Initializes a new instance of the Trictionary 
    /// class for use with serialization
    /// </summary>
    /// <param name="info">SerializationInfo objects that holds the 
    /// required information for serialization</param>
    /// <param name="context">StreamingContext structure 
    /// for serialization</param>
    protected Trictionary(SerializationInfo info, StreamingContext context)
        : base(info, context)
    {
    }

    /// <summary>
    /// Gets or sets the values associated with the specified key
    /// </summary>
    /// <param name="key">The key of the values to get or set</param>
    /// <returns>Returns the DualObject associated with this key</returns>
    public new DualObject<TValue1, TValue2> this[TKey key] 
    {
        get
        {
            return base[key];
        }

        set
        {
            if (this.ContainsKey(key))
            {
                base[key].Set(value);                    
            }
            else
            {
                base[key] = value;
            }
        }
    }
}

I've try to bold out the important bits of code but it may not really stand out depending on the font used. Essentially the value type for the dictionary is my DualObject<> class which I'll talk about soon. If this is the first time the key is being used it'll just add it to the dictionary, else it will use the Set method in the DualObject class to associate the new value of either type. So how does it accept values of either type when the value-type for the dictionary is the DualObject<> class? Well that's all implicit operator magic as shown below. [The code has been re-formatted for the browser]

/// <summary>
/// Represents a class that can hold two types values
/// </summary>
/// <typeparam name="TValue1">The first type</typeparam>
/// <typeparam name="TValue2">The second type</typeparam>
[Serializable]
public class DualObject<TValue1, TValue2> 
  : IEquatable<DualObject<TValue1, TValue2>>
{
    /// <summary>
    /// Initializes a new instance of the DualObject class
    /// </summary>
    /// <param name="value1">First value</param>
    /// <param name="value2">Second value</param>
    public DualObject(TValue1 value1, TValue2 value2)
    {
        this.FirstValue = value1;
        this.SecondValue = value2;
    }

    /// <summary>
    /// Initializes a new instance of the DualObject class
    /// </summary>
    /// <param name="value2">Second value</param>
    /// <param name="value1">First value</param>
    public DualObject(TValue2 value2, TValue1 value1)
        : this(value1, value2)
    {
    }

    /// <summary>
    /// Initializes a new instance of the DualObject class
    /// </summary>
    /// <param name="value">First value</param>
    public DualObject(TValue1 value)
    {
        this.FirstValue = value;
    }

    /// <summary>
    /// Initializes a new instance of the DualObject class
    /// </summary>
    /// <param name="value">Second value</param>
    public DualObject(TValue2 value)
    {
        this.SecondValue = value;
    }

    private bool isFirstValueSet;

    private TValue1 firstValue;

    private TValue1 FirstValue
    {
        get
        {
            return this.firstValue;
        }

        set
        {
            this.firstValue = value;
            this.isFirstValueSet = true;
        }
    }

    private bool isSecondValueSet;

    private TValue2 secondValue;

    private TValue2 SecondValue
    {
        get
        {
            return this.secondValue;
        }

        set
        {
            this.secondValue = value;
            this.isSecondValueSet = true;
        }
    }

    /// <summary>
    /// Implicit conversion operator to convert to T1
    /// </summary>
    /// <param name="dualObject">The DualObject to convert from</param>
    /// <returns>The converted object</returns>
    public static implicit operator TValue1(
      DualObject<TValue1, TValue2> dualObject)
    {
        return dualObject.FirstValue;
    }

    /// <summary>
    /// Implicit conversion operator to convert to T2
    /// </summary>
    /// <param name="dualObject">The DualObject to convert from</param>
    /// <returns>The converted object</returns>
    public static implicit operator TValue2(
      DualObject<TValue1, TValue2> dualObject)
    {
        return dualObject.SecondValue;
    }

    /// <summary>
    /// Implicit conversion operator to convert from T1
    /// </summary>
    /// <param name="value">The object to convert from</param>
    /// <returns>The converted DualObject</returns>
    public static implicit operator DualObject<TValue1, TValue2>(
      TValue1 value)
    {
        return new DualObject<TValue1, TValue2>(value);
    }

    /// <summary>
    /// Implicit conversion operator to convert from T2
    /// </summary>
    /// <param name="value">The object to convert from</param>
    /// <returns>The converted DualObject</returns>
    public static implicit operator DualObject<TValue1, TValue2>(
      TValue2 value)
    {
        return new DualObject<TValue1, TValue2>(value);
    }

    /// <summary>
    /// Sets the value for the first type
    /// </summary>
    /// <param name="value">The value to set</param>
    public void Set(TValue1 value)
    {
        this.FirstValue = value;
    }

    /// <summary>
    ///  Sets the value for the second type
    /// </summary>
    /// <param name="value">The value to set</param>
    public void Set(TValue2 value)
    {
        this.SecondValue = value;
    }

    /// <summary>
    /// Sets the values from another DualObject
    /// </summary>
    /// <param name="dualObject">The DualObject 
    /// to copy the values from</param>
    public void Set(DualObject<TValue1, TValue2> dualObject)
    {
        if (dualObject.isFirstValueSet)
        {
            this.FirstValue = dualObject.FirstValue;
        }

        if (dualObject.isSecondValueSet)
        {
            this.SecondValue = dualObject.SecondValue;
        }
    }

    #region IEquatable<DualObject<T1,T2>> Members

    /// <summary>
    /// Indicates whether the current DualObject is 
    /// equal to another DualObject
    /// Note : This will return true if either one of the 
    /// values are equal.
    /// </summary>
    /// <param name="other">The DualObject to compare with</param>
    /// <returns>True if the objects are equal</returns>
    public bool Equals(DualObject<TValue1, TValue2> other)
    {
        bool firstEqual = this.FirstValue == null ? 
            other.FirstValue == null : 
            this.FirstValue.Equals(other.FirstValue);
        bool secondEqual = this.SecondValue == null ? 
            other.SecondValue == null : 
            this.SecondValue.Equals(other.SecondValue);
        return firstEqual || secondEqual;
    }

    #endregion
}

The implicit conversions are kinda self-explanatory. These operators allow me to pass either type to the Trictionary (in the sample, that'd be string and double). They also allow me to cast back the DualObject<> to either type (string or double in my sample code). The most interesting bit might be my Equals implementation which might seem to be wrong at first sight. Should that || comparison have been an && comparison? Technically yes, it should have been that but for the sake of my Trictionary class, two DualObject<> objects are considered equal if either of their two values match. This is because I want ContainsValue to work correctly. The following code will make it clear :

trictionary.Clear();

trictionary[19] = "bbb";
trictionary[19] = 11.5;

Console.WriteLine(trictionary.ContainsValue("bbb"));
Console.WriteLine(trictionary.ContainsValue("aaa"));
Console.WriteLine(trictionary.ContainsValue(11.5));

The above code will output True, False, True as expected. Had I chosen not to implement Equals this way, I'd have had to re-implement ContainsValue and rewrite a lot of comparison code (something I wanted to avoid). Also I do not expect anyone to use my DualObject<> class outside of Trictionary. And even if they do, then I think it may actually help them in their cause to keep the Equals behavior in this manner.

Feel free to post any comments and feedback. And I'd like to specially mention that any feedback here on the need for a Trictionary type class should really go to Joe Enos and not to me *grin*

[Note that the downloadable code in the zip file is properly formatted as I did not have to add extra line-breaks for an improved browser display]

References

History

  • March 11, 2009 - Article published

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here