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 :

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.

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.

Implementation details

///<summary>/// Represents a dictionary with two distinct typed values per key
///</summary>///<typeparamname="TKey">The type of the dictionary key</typeparam>///<typeparamname="TValue1">The type of the first value</typeparam>///<typeparamname="TValue2">The type of the second value</typeparam>[Serializable]
publicclass 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>///<paramname="info">SerializationInfo objects that holds the
/// required information for serialization</param>///<paramname="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>///<paramname="key">The key of the values to get or set</param>///<returns>Returns the DualObject associated with this key</returns>publicnew DualObject<TValue1, TValue2> this[TKey key]
{
get
{
returnbase[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]

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 :

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]

License

Share

About the Author

Nish Nishant is a Principal Software Architect based out of Columbus, Ohio. He has over 17 years of software industry experience in various roles including Lead Software Architect, Principal Software Engineer, and Product Manager. Nish was a Microsoft Visual C++ MVP between 2002 and 2015.

Nish is an industry acknowledged expert in the Microsoft technology stack. He authored C++/CLI in Action for Manning Publications in 2005, and had previously co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is vastly experienced in team management, mentoring teams, and directing all stages of software development.

Contact Nish : If you are interested in hiring Nish as a consultant, you can reach him via his google email id voidnish.

I like the (ab)use of the implicit operator.
A few questions/ideas:
1. Why not use a struct for the DualObject? That's how MS implemented the KeyValuePair.
2. I think DualObject should be called Tuple because that's what it is, and it's a well know data type in functional programming. In .Net4 it'll already be included.
3. To help with multithreading scenarios, you should think about making the DualObject immutable. This can be achieved by making all the members readonly, and making the mutating functions return a new instance of a DualObject with the new values. Something like this:

I'm wondering why you made the getters for FirstValue and SecondValue private though? Wouldn't it have been cleaner to access the values through those properties, rather than casting them like you did inside Console.WriteLine? Access via properties would also help disambiguate things (for the developer) if the one of the two types is implicitly convertible to the other, like int and short, for example.

Yeah I should perhaps have exposed those properties so people can explicitly access a specific value. But you are right, if the 2 values are int and short and they try to cast that to a float, you'd probably have to do an extra level of casting to get a specific value.

float f = (float)(int)value;

whereas they could probably do :

float f = (float)value.FirstValue;

I haven't tried it out yet but I think the compiler would resolve this without hitting an ambiguity issue.

That's quite elegant. One question--do you think something somewhere should throw and exception if TValue1 and TValue2 are the same type, since the implementation can't handle that scenario? Perhaps the implicit operators could do that check, so the programmer could still explicitly obtain the values from your DualObject class?

One question--do you think something somewhere should throw and exception if TValue1 and TValue2 are the same type, since the implementation can't handle that scenario? Perhaps the implicit operators could do that check, so the programmer could still explicitly obtain the values from your DualObject class?

Hey Marc,

The problem here is that the error is at the compiler level. I have many overloads of the constructor, the Set methods and the operators that differ only in the type/order of the arguments (either TValue1 or TValue2 or both). If TValue1 and TValue2 are same, it will result in identical methods which are ambiguous to the compiler. Example, I have both :

public DualObject(TValue1 value)

and

public DualObject(TValue2 value)

Now if both types were string, I am essentially asking the compiler to give me 2 constructors both of type :

public DualObject(string value)

Of course it'd have been cool if the compiler was smart enough to realize this duplicability and to generate just the one method/constructor. If it did that my code would work without issues. [In C++, that's what would have happened had I used templates].

I had a similar problem some time ago and my solution was a bit different.
What I did was to implement a generic Pair<T, U> class which could just hold two members of different types. With that my dictionary looked like this: Dictionary<string, Pair<int, string>>. With this I could even make strange things like Dictionary<Pair<long, bool>>, Pair<int, string>> and both values could be of the same type.

The only thing that annoyed me was that it looked really ugly (as you can see from my samples). So your approach might not be as flexible but by far more readable.

> Note that I have not overridden Object.Equals, so it's not necessary for me to
> override GetHashCode.

Sure, but I fail to see how it remains correct (btw, object.equals should be implemented). Whenever you trigger an implicit (say sort on something different for some other client piece of code, non-generic use etc), or even explicit (I)EqualityComparer it should eventually bomb out.

[all a mess really because copy construction and equality as well as equivalence are a disturbed concept in VMs for ages. And then it ripples to language and finally to the dev designing types, the latter actually not being isolated from knowing other bits of the framework operation to achieve correctness. Add another losing battle of container and many iterator concepts not being well designed, it is a full-blown runtime and semantic handicap. ]

> Well considering I haven't overridden Object.Equals I doubt if this would matter. Comparisons would be
> purely reference based.
> On the other hand if a collection explicitly attempts to use the IEquatable version of Equals it knows > > that there's no guarantee regarding GetHashCode. So I still don't see why this is a problem.

You can easily see it broken as stated earlier; simply try some of the non-generic collections or numerous comparers.

Instead of a DualObject you have a mutable 'iffy-equality' pair defined here, as such the hash code is easily suspect. You will hit on hash usage if you hit any possible equality-related comparisons based on system.object interface, anywhere.

Put simply, you are implementing equality here, and equality must be abided by the System.Object interface if you are to avoid spurious bugs and behaviour in expecting a pure IEquatable Equals equality definition and especially mutable pair.

In short, IEquatable binds to hash code, just the way Object.Equals binds to hash code. Since runtime implements those for you and compiler will mislead if you try, you will end up thinking it is safe because IEquatable's strongly typed and cast-less version will always be called. It won't, and the answer is depends on when and where the type is used (ie. what other part of frameworks or type of collection or equality comparers possibly used or how you extent the TrictX ).

Granted, making your dual-x internal or private or inaccessible to other uses would make it safe But the Dictionary with keys that can end up wrongly compared by extending a source is just as easy to get into trouble with.

Equality will be spuriously 'defined'. Shouldn't the equality once defined be abided by all *equality-related* interfaces to that type, including operator == and more?

> Are you talking specifically about my class or about the BCL in general?

BCL in general and how it fails to communicate a fundamental type design issue while presenting a 'managed for us' picture.

> 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.

This is exactly the problem, as well as any possible extension of Trictionary type itself (which I believe is a bad idea Joe

Say I have 2 objects ("aa", 10) and ("bb", 10). Now your implementation would return false. I would want them to still return true.

But I get what you are saying, and for the ContainsValue issue, your implementation may work just fine. Though I do see one problem in the fact that you compare with null - this will not work for value types. For example if TValue2 is double, the default value is 0.0 and Equals returns false in this case. And the compiler won't let you compare with default(TValue2) either.