The second feature listed in What’s new in C# 7 is Tuples. This is actually a wholly new feature that reinvents the tuple for .Net. I’ve always appreciated the idea of tuples, but the existing System.Tuple was inelegant to say the least.

Upfront Notes:

  1. You currently have to pull in the nuget package System.ValueTuple even with VS 2017.
  2. The format Tuple in this article means I’m specifically talking about the .Net class. If it is formatted like regular text, then I’m referring to the general concept.
  3. Don’t get too hung up on the code snippets I’m presenting. They are there to show how Tuple and ValueTuple work and that is it. Don’t infer the code snippets themselves to be good practice.
  4. This is just an introduction and “getting the word out”, read more here: C# Tuple types

What is a tuple you may ask? It’s a math concept. Namely:

An n-tuple, sometimes simply called a “tuple” when the number n is known implicitly, is another word for a list, i.e., an ordered set of n elements.
Wolfram MathWorld

That sounds an awful lot like a fixed length array, no? Well, it pretty much is. What the Tuple type in .Net gets you is the ability to have a strongly typed list that mixes types. Like so:

    var myCustomer = new Customer(); // just assume we got the customer from a repository.
    var messageText = "Account Overdue";
    var message = new Tuple<long, string, Customer>(1, messageText, myCustomer);

You then consume the values you put in the Tuple like so:

    Console.WriteLine($"Message {message.Item1}: Customer {message.Item2.Name} : {message.Item3}");

I just accidentally proved the point I was going to laborously drone on about later. Ok, my plan for this article just got reorganized.

Notice how I’m accessing the values in the Tuple using Item1, Item2, Item3(not zero based!)? Also see that when I created the Tuple, I put a string in Item2, a Customer in Item3. While typing the Console.WriteLine, I proceeded to accidentally reverse in my mind the position of the string and the Customer objects. That’s because I started out with the order <long, string, Customer> and then changed my mind and decided to put the Customer second and string third, but then decided to flip it back again. Because I was thinking about the overall example and interupted the flow with the the note about showing you how to consume the Tuple, I then kept the reversal in my mind and got it wrong. See, it’s easy to mess it up even in a trivial example!

In Visual Studio, Intellisense would have given me the hint I was on the wrong path as the Name property would not have been a property of Item3. If I had used simpler types such as <long, string, double> or, worse ,<long, string, string>, then the error would not have been evident and would have required testing the code to see it. My first verification tool (my compiler) has been defeated, it’s up to the more expensive tools (time and/or money) to do it for me. This is the sort of stupid, easily overlooked error that results in nasty, hard to find bugs that slip past QA and into production.

The other point to consider with the existing Tuple is that it’s a generic and you can’t escape that. Generics are good, but when you start nesting them, you can lose your mind. Consider a List of Tuple.

    var myList = new List<Tuple<long, Customer, string>>();

Now add some values to it:

    myList.Add(new Tuple<long, Customer, string>(1, myCustomer1, "Account Overdue"));
    myList.Add(new Tuple<long, Customer, string>(2, myCustomer2, "Credit Approval Pending"));

It’s starting to look clunky, repetitive, and not generally useful. Now imagine you wanted a generic type as one of the members of the tuple? Say you wanted to add the list of open orders?

    var myList = new List<Tuple<long, Customer, string, IEnumerable<Order>>>();
    myList.Add(new Tuple<long, Customer, string, IEnumerable<Order>>(1, myCustomer1, "Account Overdue", openOrdersCustomer1));
    myList.Add(new Tuple<long, Customer, string, IEnumerable<Order>>(2, myCustomer2, "Credit Approval Pending", openOrdersCustomer2));

Angle bracket mania! This is getting to the point of unusable if it’s not already there. To date, there’s been no syntactic sugar that I’ve seen to avoid the form new Tuple<long, Customer, string, IEnumerable<Order>> for generic Tuple. Even using the List initializer syntax, it doesn’t improve much. It’s legible, but there’s still a lot of noise.

    var myList = new List<Tuple<long, Customer, string, IEnumerable<Order>>>
    {
    new Tuple<long, Customer, string, IEnumerable<Order>>(1, myCustomer1, "Account Overdue", openOrdersCustomer1),
    new Tuple<long, Customer, string, IEnumerable<Order>>(2, myCustomer2, "Credit Approval Pending", openOrdersCustomer2)
    };

Before going further: Some notes on tuples.

  1. If you have a list of all the same type of object, then classic .Net Tuples really don’t help you more than arrays or any other indexable collection type. At least with an indexable type, I could use named constants for my indices. Which brings me to my next point:
  2. Tuples are not a replacement for defining custom types(e.g. classes) as part of your system model. If you wouldn’t use an array to replace a type, then don’t use a Tuple either. Broadly speaking: use well defined types for the parameters or return types for public methods. The existing Tuples are usually best used when you need a bunch of values of different types that are used in only one place in the code and an anonymous type isn’t suitable. So, either inside a single method’s scope or as parameters or return types for a private method. This largely applies to ValueTuple as well, but we’ll see that they are much better.

So, what is ValueTuple?

ValueTuple addresses the cleanliness of the code first and foremost. It also has the vastly improved the ability to help with the ItemX problem.

Let’s look at the cleanliness aspect first. My list of <long, Customer, string, IEnumerable<Order> now becomes:

   var myList = new List<(long, Customer, string, IEnumerable<Order>)>
   {
       (1, myCustomer1, "Account Overdue", openOrdersCustomer1),
       (2, myCustomer2, "Credit Approval Pending", openOrdersCustomer2)
   };

Already, we’re seeing value in more concise code and less repetive boiler plate code.

  1. Tuple disappeared altogether. For a single tuple, you would declare it like so: var myTuple = (1, myCustomer1, "Account Overdue", openOrdersCustomer1);. Not even a new.
  2. No more new Tuple<long, Customer, string, IEnumerable<Order>>.

IEnumerable<Order> is causing a little bit of angle bracket grief still, but that’s just because I’m pushing the example’s limits.

But wait, there’s more!
Ron Popeil, Billy Mays, etc.

As I said, it helps with the ItemX problem as well. You can give your own names to the items in the tuple. You can even use the name inside your initializer, although it’s purely optional. You can skip the names in which case you would still use Item1, Item2,… ItemN.

   var myList = new List<(long messageId, Customer customer, string messageText, IEnumerable<Order> openOrders)>
   {
       (1, myCustomer1, "Account Overdue", openOrdersCustomer1),
       (messageId: 2, customer: myCustomer2, messageText: "Credit Approval Pending", openOrders: openOrdersCustomer2)
   };

A downside: in the initializer, it’s purely for your own reference. What matters is the order. If I get the names wrong, it won’t throw a compiler error. It will generate a warning*, but the code will run just fine based on the order. Even if I get the names backwards, the ordering takes over. This would still work for the second item (switching the names openOrder and customer only):

    (messageId: 2, openOrders: myCustomer2, messageText: "Credit Approval Pending", customer: openOrdersCustomer2)

But switching the variables around wouldn’t work because the types are now wrong:

    (messageId: 2, openOrders: openOrdersCustomer2, messageText: "Credit Approval Pending", customer: myCustomer2)

If the types were the same, I just caused myself a problem. I wish it caught it, but I’d have the same problem if I didn’t use the names.

Where this helps is in the consumption. I now use those names to refer to the values in the tuple.

    Console.WriteLine($"Message {message.messageId}: Customer {message.customer.Name} : {message.messageText}");

This is the benefit of the names. If you come back to the code later to update that message, you don’t need to go dig and refamiliarize yourself with the order in which values were assigned. Done well, it’s one more step towards self-documenting code.

Naturally, there would be little benefit to these if you couldn’t pass the tuple around. We could put our little print function in its own method:

    private static void PrintMessage((long messageId, Customer customer, string messageText, IEnumerable<Order> openOrders) message){
        Console.WriteLine($"Message {message.messageId}: Customer {message.customer.Name} : {message.messageText}");
    }

Now it’s getting wordy, so hopefully you can see why you’d want to use your own type or even pass parameters separately in most cases.

My Take

Tuples in .Net can now come out of the shadows. I would still not use them as part of my model. If you have to refactor or expand the properties you need, you have to go change everywhere you declare it. With the ability to name each item, they become superior to arrays for some cases, even where all members are the same type. I can see value in using these lambdas or with another new feature: [Local Functions] (https://docs.microsoft.com/en-us/dotnet/articles/csharp/csharp-7#local-functions). Local functions are a whole other topic.

  • Do consider ValueTuple for groups of values that make sense together and will only be used within the scope of a single method (example below).
  • Do consider ValueTuple when passing values to or returning from private methods.
  • Do Not use ValueTuple when passing values to or returning from public, protected, or internal methods without carefully considering why you aren’t using a well defined type.

I’m not categorically ruling out ever using ValueTuple as part of the public api of an object. However, it should only be done when it helps communicate the intention and does not create a later refactoring issue. Remember the DRY principle: “Don’t Repeat Yourself”. Every time you pass a tuple into another method, you’re repeating the act of declaring its members. Even when returning it, you are declaring its members every time, var may just be hiding that declaration for you. The

More elaborate examples

Given some function that implements Euclid’s algorithm for finding the greatest common divisor of two positive integers m and n, let’s write another function that will test it for us. I won’t show an implementation of Euclid’s algorithm, we’ll just assume we have it. I don’t want to ruin anyone’s homework problems with spoilers!

So, we have a bunch of test cases we need to run but we don’t want to repeat the same code. Let’s use a list of tuples to define some test cases and another to define the results of the test case.

Keep in mind my goal right now is showing `ValueTuple, not how to write unit tests!

A test case is defined as this tuple: (long m, long n, long expected)

A test result is defined as this tuple: ((long m, long n, long expected) testCase, long actual)

Notice it is nested. For this example, I’m just showing you what is possible. If I wanted to add a string to the test case that described it, I would have to update everywhere I defined the tuple.

I’m going to show three helper functions first.

A super trival helper that merely creates and returns a tuple for us.

    private (long m, long n, long expected) CreateTestCase(long m, long n, long expected)
    {
        return (m, n, expected);
    }

A less trivial helper that shows the concept of deconstruction. That is, assigning the values of a tuple into separate varibles.

    private static void PrintTestCase((long m, long n, long expected) testCase)
    {
        // Deconstructing a tuple into variables
        (long m, long n, long expected) = testCase;
        System.Diagnostics.Debug.WriteLine($"Evaluating greatest common divisor of {m} and {n}. We expect {expected}");
    }

And a trivial helper that will append the result to a string builder so we can print the test cases that fail.

    private static void AppendError(StringBuilder stringBuilder, ((long m, long n, long expected) testCase, long actual) failure)
    {
        stringBuilder.Append($"{failure.testCase.m} and {failure.testCase.n}");
        stringBuilder.Append($" should have the common greatest divisor {failure.testCase.expected}");
        stringBuilder.AppendLine($" but we received {failure.actual}");
    }

And now a method to go test my function.

    public void TestCommonDivisorsWithValueTuple()
        {
            // arrange
            var testCases = new List<(long m, long n, long expected)>
            {
                // below we see using name prefixes, but it doesn't matter.
                (m:999L, n:444L, expected:111L),
                (m:999999L, n:333L, expected:333L),                
                (m:1232453L * 3, n:1232453L * 2, expected:1232453L),
                (m:1232453L * 3, n:1232453L, expected: 1232453L),
                (m:1232453L, n:1232453L, expected: 1232453L),
                /* names don't matter when assigning */
                (long.MaxValue-1, long.MaxValue/2, long.MaxValue/2),
                /* This next case uses the wrong name for "expected".
                 * It creates a compiler warning and not a compiler error.
                 * Otherwise, "fred" will be used as "expected" without any problems though.*/
                (m: 1231, n: 1231, fred: 1231),
                /* This next test case may fool you. The order counts, not the names. 
                 * Like above, the code still runs but the test passes based 
                 * on the order. You still get a compiler warning. */
                (m:20L, expected:16L, n:4L),
                /* Use a helper function that returns a tuple.*/
                 CreateTestCase(1232453L, 1232453L, 1232453L)
            };
            
            var failures = new List<((long m, long n, long expected) testCase, long actual)>();

            // act
            foreach (var testCase in testCases)
            {
                PrintTestCase(testCase);
                var result = EuclidsAlgorithm.FindGreatestCommonDivisor(testCase.m, testCase.n);
                if (result != testCase.expected)
                {
                    failures.Add((testCase, result));
                }
            }

            // "assert" - not getting hung up on the fact that results were actually evaluated in the loop above.
            StringBuilder stringBuilder = new StringBuilder();
            if (failures.Any())
            {
                stringBuilder.AppendLine("The following test cases failed:");
                foreach (var failure in failures)
                {
                    AppendError(stringBuilder, failure);
                }
            }
            Assert.IsFalse(failures.Any(), stringBuilder.ToString());
        }

* - This is indeed a compiler warning. If you compile with warnings set to be errors, it will be caught as an error. I’m one of the many people who advocate that your non-trivial projects should always be set to break on compile if there are warnings. If you don’t deal with them early, you end up never dealing with them. Many warnings are strong indicators that you may have a problem, but the compiler can’t be sure.