Understanding C# Tuples

Wednesday Apr 5th 2017 by Gavin Lanata
Share:

With the arrival of C# 7, tuples have changed quite a bit. If you haven't been using them, read this article; you might reconsider their use.

If I were to draw up a list of the top debated and questionable features of C# prior to C# 7, tuples would be on that list. Being a part of the developer community, I can attest that tuples have indeed come up in conversation many times over the years.

Do you use them? Should you use them? If you do use them, are they genuinely helpful and would you stick by them? Or, would you prefer that any given code base you're working on to be completely tuple free, and you recoil at the sight of them!?

There are several questions there, and questions I can't answer, but only for myself.

However, with the arrival of C# 7, tuples have had quite a bit of work done on them.

Before we go into some code, and, depending on your version of Visual Studio, you may need to grab the following package from NuGet…

System.ValueTuple

This is required for Visual Studio 15 Preview 5 and earlier releases.

Let's Code!

Before we look at this new feature of C# 7, let's quickly remind ourselves what the tuples of yesterday look like…

static void Main(string[] args)
{
   Tuple<int, string> anOldTuple =
      new Tuple<int, string>(10, "count");
}

In this short code snippet, we can see how to bring to life a standard tuple, with an int and a string passed in through the constructor on being instantiated.

Once this tuple has been instantiated, its fields are read only. And, the naming of those fields would be something like Item1, Item2 and so on, which we can see in Figure 1.

A standard tuple
Figure 1: A standard tuple

Given the naming, the field doesn't really give us any clues as to what meaning any data stored in those fields might have. We therefore come to one of the primary concerns with using tuples. But, on the other hand, is this potentially better than the overhead of writing a class or struct for very simple data transfer operations?

Now, let's compare the tuple we saw earlier, with the new tuple…

static void Main(string[] args)
{
   (int value, string name) newTuple = (10, "count");
}

We can see from the code above that firstly, declaring our tuple appears much cleaner. But, what does accessing fields in this tuple look like…?

Access fields on tuple 2
Figure 2: Access fields on tuple 2

Our fields now have much more meaning as relevant naming is present. However, do note that the original naming convention can re-appear if the keyword var is used; this is demonstrated below…

static void Main(string[] args)
{
   var newTuple = (10, "count");
}

New tuples with old field naming when var is used
Figure 3: New tuples with old field naming when var is used

So, given what we've looked at so far, would you say you'll be using tuples more often? For myself, I would say yes. And this is where, by design, they are ideally suited to appear—the return value on a private or internal method…

class Program
{
   static void Main(string[] args)
   {
      var result = GetData(new int[6] { 1, 3, 5, 6, 11, 20 });
      Console.WriteLine($"count : {result.count}");
      Console.WriteLine($"total : {result.total}");
      Console.WriteLine($"first value : {result.first}");
   }

   private static (int count, int total, int first)
      GetData(IEnumerable<int> values)
   {
      int count = values.Count();
      int total = values.Sum();
      int firstValue = values.First();

      return (count, total, firstValue);
   }
}

From the preceding code, we can avoid the work of creating a type just for this method, which is internal to the class and only called once. At the same time, we can keep meaningful naming of the fields, thus keeping our code readable.

And, here is the result of the previous code running…

The output from our private method, which returns a tuple
Figure 4: The output from our private method, which returns a tuple

Speaking for myself, this is especially useful when returning multiple values via Task<T>, like so…

private static async Task<(int min, int max)>
   GetMinMax(IEnumerable<int> values)
{
   await Task.Delay(1000);
   return (values.Min(), values.Max());
}

Because I find myself often working with asynchronous methods for UI-related work, where such methods cannot have an out parameter, this is a boon on many levels.

Conclusion

There are many features in C# 7 which can help you in your day-to-day coding duties, and this is definitely one I'll be making increased use of, and I hope it can help you too. As always, if you have any questions, you can find me on Twitter @GLanata.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved