The Care and Feeding of Tuples in C#

Tuples have many uses in languages like F# and Python; they underlie relational theory. The .NET support for tuples seems rather limp by comparison. Why is this, and what are the obvious uses for tuples in C#? Tom Fischer takes on the cause of C# tuples and tackles the 'what', 'why' and 'when'. He makes the case for using them, while explaining the tribulations of the .NET implementation in the past.

Growing up as a C# tuple wasn’t easy for anyone. Upon arrival, it was juvenile and prone to moodiness, so many .NET developers didn’t want it around. Those days might be over, because the .NET Framework 4.7 includes a more mature implementation that promises to help developers write better code.

This article discusses the what, why, and when of turning tuples into useful citizens in C# applications.

What?

Developers who aren’t already familiar with the way that tuples are used in other languages such as F#, Haskell, Python and Scala sometimes struggle with them in C#. Unless they are familiar with a dynamically-typed or functional language, object-oriented developers understandably question the tuple’s raison d’être.

In the simplest programming terms, a tuple is a data structure that contains an ordered sequence of types. This code demonstrates the two ways of creating tuples as first introduced by C#.

This syntax may look to you as if C# treats a tuple eerily like a class, and that’s not a gross misconception. In fact, tuples behave much like a class with a few exceptions. For example, they are not defined or statically checked. Unlike a class definition, You will not find one in a code file.

This C# implementation of a Fibonacci number calculation demonstrates a main use case of a tuple: spinning up lightweight objects for manipulation.

Another pervasive use for tuples is to get around a familiar C# coding hassle: returning multiple entities from a method without a specialized class/struct or an out parameter modifier.

One of the more vexing aspects of employing C# tuples comes from the confusion arising from the change in the way they are implemented in .NET

C# Tuples: The Early Years

In early 2010, the release of the .NET Framework 4 allowed C# developers to leverage tuples as shown in FibonacciViaTuple. Although it was useful to be able to combine values and objects in a lightweight data structure, it felt clunky compared to the implementations in other languages. Consider this Python 3.6 implementation.

Our fibonacci_via_tuple example highlights the frustrations that C# developers face when working with tuples. Firstly, the mechanism whereby tuples were created seemed awkward. Why do I need to either call some utility function or manually instantiate to create a tuple? Can’t C# just spin them up like an anonymous function or some such? Next, Python’s simple index-based content access looked brilliant when compared to C#’s use of labels such as Item1, Item2, Item3, etc. Can’t C# do better than insisting on names sounding like Thing 1 and Thing 2 from Doctor Suess?

The answer from Microsoft, after several years of thoughtful consideration, was “Oops! Yes, you’re right”.

C# Tuples: The Middle Years

The System.ValueTuple NuGet package of mid-2016 signaled the arrival of a more mature C# tuple. By adding this type to the .NET 4.7 Framework, Microsoft removed several shortcomings of tuples 1.0. The following snippets highlight these changes.

Creating a C# tuple became easier.

Notice the lack of a static creation function or new keyword? Yes, all gone.

The tuple4 assignment demonstrates the other major enhancement – the means of labeling the contents of tuples. Accessing a tuple’s contents by such labels, officially referred to as named properties, became a viable alternative to Python’s indexing approach.

C# Tuples: Growing Pains

Sadly enough, the protracted adolescence of tuples is still not over. C# tuples still suffer from growing pains. The first such developmental challenge is subtle. Consider the below line of code.

It can’t compile for a few irritating reasons. Firstly, and most obvious, C# tuples do not include the built-in support for equality as found in languages like Python. Second, Tuple.Create spins up a different type from (1, “Test”). There is no good reason for this: it merely reflects their different implementations over the years.

Note: Even if you ensure that the tuple types match exactly, this does not placate the compiler, which still complains when presented with matching tuple types, such as, (123, “Some String”) == (123, “Some String”).

Despite resembling a class, a tuple is not a full-fledged class. The way that they handle serialization exemplifies this point. Consider this code that creates what looks like an anonymous class for conversion to a JSON object.

Inspection of the output shows that the named properties returned to our Item1, Item2 and Item3 friends. Underneath the covers, the System.ValueTuple instance retained its property names.

Named properties are not the same as class property names. We will revisit this concept in the next section of the article.

C# tuples are not immutable. The below code will compile without issue and behave as expected.

Finally, the System.ValueTuple and all the supporting magic that is served up via extension functions is not in .NET Core; only the original implementation of tuples is supported. Maybe this is not the worst growing pain, but definitely a hassle when porting code from a .NET 4.7+ Framework to Core.

Why

A simple demo can highlight why tuples might improve C# code quality. Imagine we have a library doing some work with user information. Our first take starts by creating a class and a method for manipulating it.

As we ponder our design, UserInfo looks a little heavy for our needs. Rather than creating a class maybe a tuple offers a more practical alternative as shown below.

After adding GetUserInfo2, the next snippet demonstrates how little our move from UserInfo to the (int id, string Name, DateTime LastUpdate) tuple cost with respect to usability. Everything behaves as expected, except the implicit ToString.

Whether or not the different outputs poses a problem depends a great deal on developer preferences. If always overriding ToString, then the tuple’s built-in display of values becomes superfluous. If not, debugging with a tuple looks more friendly than a worthless class name.

Writing small functions highlights the tuple’s flexibility. For example, GetTopSalesUserName can consume almost any two collections of any types.

The above also highlights a weakness. Tuples allow collections of any type that satisfies the parameter’s signatures. Nothing prevents a developer from incorrectly building userSales. Maybe the double is a sales number, maybe not? The potentially trickier and bug-generating aspect of GetTopSalesUserName arises from named properties.

Consider the following snippet.

The complier did not complain when we added the (int Id, string Password, DateTime LastUpdate) tuple to userInfos. It only checked that the tuple’s constituent types match. It did not concern the compiler that Item2 was named “Password” or “Name”. Did you remember that the named properties of a tuple are not equivalent to class or struct property names?

When

With a better understanding of C# tuples, the next question becomes “When would I use them?” Isn’t it better to fabricate an entity as an anonymous class, struct/class or tuple? Experience suggests that the design criteria comes down to scope. The chart below summarizes how scope might influence the type of a limited use entity.

While the .NET compiler may not enforce any of the above recommendations and developers can rightfully disagree with them, our earlier discussions suggest that tuples shine within the confines of a component. Tuples external to a component lose the protective type safety of a class. They also give up some other ‘nice to have’ class features such as serialization and the overriding of ToString.

Performance isn’t much of a consideration when you are deciding between anonymous classes, tuples and classes beyond edge cases. Let’s illustrate this with the familiar example of manipulating ten thousand lightweight entities.

The output reproduced below supports our unscientific claim. Performance was insignificant when selecting the entity type. Is the 0.05 second runtime for an anonymous class much worse than a tuple’s 0.01 seconds when working thru 10,000 items? I doubt it.

Conclusion

We briefly covered the ‘what’, ‘why’ and ‘when’ of C# tuples. Tuples can improve C# code; but, they require a little caution. They may not be the sematic equivalent of the tuples that are found in other languages; but they serve their purpose reasonably well.