In-Depth

What's So Great About Tuples?

String functions, integer functions ... booorrring! Tuples in C# 7.0 -- let's explore what makes them infinitely more exciting.

String functions return a string. Integer functions return an integer. Ugh, so boring. Fortunately, Microsoft knows all about boring, and it decided to do something about it: tuples. With the new tuple technology included in C# 7.0 and Visual Studio 2017, you can craft functions that return more than one value.

Of course, C# has always allowed you to return more than one value from a single function. The most natural way to do this was to build a structure containing the distinct elements, and return an instance of that structure:

public struct MixOfTens
{
  public string TextPart;
  public int NumPart;
}

public MixOfTens GiveMeTen()
{
  MixOfTens result;
  result.TextPart = "Ten";
  result.NumPart = 10;
  return result;
}

This structure-based approach is just that: structured. If you wanted to return those values in a way that was a little more on-the-fly, you could employ out-parameters, one for each value coming back from the function:

public void GiveMeTen(out string textPart, out int numPart)
{
  textPart = "Ten";
  numPart = 10;
}

Tuples provide a third way of returning multiple function values, one that combines aspects of the structure-based and on-the-fly options already mentioned. With tuples, you define the values to be returned as part of the function declaration, a bit like the out-parameter variation. But to the caller, the data coming back appears as an instance with distinct value members, just like the structure-based approach.

Beyond being a new way of doing something you could already do before, tuples offer a few other advantages over the traditional multi-value methods.

  • Starting with Visual Studio 2017 and C# 7.0, the language includes a built-in syntax for declaring and using tuples, whether you're dealing with methods or independent variables.
  • Tuples look like any other instance with members, and you can create and interact with tuples as needed, even passing them to functions that accept tuple arguments.
  • Tuples are ordered sets of values, and their members can be accessed by position-based names, even in the absence of intentional field names.

Behind the scenes, tuples are implemented using standard generic objects. For example, the string-int tuple mentioned earlier could be declared using the generic System.Tuple class:

public Tuple<string, int> GiveMeTen()
{
  return new Tuple<string, int>("Ten", 10);
}

While this code works just fine, the new tuple syntax included in the latest release of C# provides a more straightforward style that's easier on the keyboard and easier to manage in code.

A word of warning about the examples in this article: All code was developed using Visual Studio 2017 Release Candidate. There is a miniscule-yet-still-possible chance that changes might occur before the final release of the product.

Although Visual Studio now includes support for tuples, you still need to download and install the tuple library as part of your project or solution. If you just want to try out tuples, start by creating a C# Console App. Save your project to disk, and then access the Visual Studio Tools | NuGet Package Manager | Manage NuGet Packages for Solution menu command. When the NuGet Package Manager window appears (see Figure 1), select the Browse tab, enter Tuple in the search field and select the System.ValueTuple item from the results. Select your project from the checklist of projects that magically appears and, finally, click the Install button.

[Click on image for larger view.] Figure 1. Installing Tuple Library

Once you assent to the various terms and conditions, Visual Studio adds the System.ValueTuple assembly to your project's references (see Figure 2).

[Click on image for larger view.] Figure 2. Adding System.ValueTuple Assembly to Project References

Now you're ready to build a tuple-returning function. The syntax is quite similar to a standard function that returns a single value, but the multiple return types appear in a set of parentheses, both in the function declaration, and as part of the return statement:

public (string, int) GiveMeTen()
{
  return ("Ten", 10);
}

The easiest way to intercept this result is through an implicit variable, one declared using the C# var keyword:

var result = GiveMeTen();

The returned instance includes a member field for each strongly typed element of the tuple. In the GiveMeTen example, the first member is a string containing "Ten," and the second is an integer containing 10. By default, these members have positional names: Item1, Item2 and so on:

// ----- Writes out the string "Ten":
Console.WriteLine(result.Item1);

// ----- Writes out the integer 10:
Console.WriteLine(result.Item2);

Of course, default names are boring, and as mentioned earlier, we're done with boring return values. You can include new default member names as part of the return type declaration for your tuple function:

public (string TextPart, int NumPart) GiveMeTen()
{
  return ("Ten", 10);
}

The data returned from this updated code still includes Item1 and Item2 members, but you can also use the custom TextPart and NumPart field names:

// ----- Writes out the string "Ten":
Console.WriteLine(result.TextPart);

// ----- Writes out the integer 10:
Console.WriteLine(result.NumPart);

Did you notice how I called the TextPart and NumPart monikers "default member names"? That's because the names are suggestions only. When you capture the return data from the function, you can craft your own compatible tuple variable, complete with locally relevant member names:

(string Literature, int Mathematics) result = GiveMeTen();
Console.WriteLine(result.Literature);
Console.WriteLine(result.Mathematics);

This is great and all, but part of the promise of tuples is the ability to return multiple values from a function, not just a last-minute object with multiple members. Fortunately, C# lets you capture the tuple members as independent variables. The syntax is similar to local tuple variable declaration, but it leaves out the name of the target merged object:

(string simpleString, int simpleNum) = GiveMeTen();
Console.WriteLine(simpleString);
Console.WriteLine(simpleNum);

Returning function values isn't the only reason tuples exist. As mentioned earlier, tuples are standard instances of generic types, a technology that's been part of the C# language since 2005. This means that you can create instances of tuple variables alongside your regular boring variables, modify them, compare them and pass them to methods as needed:

(string Name, int Age) person1 = ("Alice", 32);
(string Name, int Age) person2 = ("Bob", 36);
IntroducePeople(person1, person2);

When crafting methods that accept tuples as arguments, replace the standard data type with a tuple-type:

public static void IntroducePeople((string, int) friend1, (string, int) friend2)
{
  // ----- Because the parameters did not specify custom
  //       member names, use the Item1 and Item2 defaults.
  if (friend1.Item1 == friend2.Item1)
  {
  }
}

While you can compare tuple members to like-typed variables and expressions using standard C# comparison operators, comparing entire tuples with each other requires use of the Equals method, included in every tuple instance:

if (friend1.Equals(friend2) == true)...

Another fun use of tuples, if you consider data manipulation fun, is to return them from a LINQ query. In C#, when you want to return objects with a subset of members from the original objects, you must create an anonymous or named instance using the new keyword:

List<Employee> allEmployees = new List<Employee>()
{
  new Employee { ID = 1L, Name = "Fred", Salary = 50000M },
  new Employee { ID = 2L, Name = "Sally", Salary = 60000M },
  new Employee { ID = 3L, Name = "George", Salary = 70000M }
};

var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  select new { EmpName = oneEmployee.Name,
               Income = oneEmployee.Salary };

Because tuples are object instances with named members, you can replace the class-returning LINQ query with one that returns tuples:

var wellPaid =
  from oneEmployee in allEmployees
  where oneEmployee.Salary > 50000M
  orderby oneEmployee.Salary descending
  select (EmpName: oneEmployee.Name,
          Income: oneEmployee.Salary);

var highestPaid = wellPaid.First().EmpName;

The examples presented so far have included two members per tuples, but you can add more comma-delimited items as needed. But be warned: If you create a tuple with 27 members, you might be trying to compensate for a fear of well-crafted data structures.

At this point, you're thinking, "Tuples are amazing. Down with classes! Up with tuples!" Hold on there, Mr. Reverse-Luddite. Tuples are extremely useful for lightly coded, data-only objects that don't require much in the way of processing logic. But they lack the technological breadth that true classes and structures offer. Tuples have no methods, no protected or internal members, and no means of implementing interfaces.

And yet, tuples are useful, just as variables and class instances are useful. In fact, the C-language class and structure definitions from which C# gets its core types are really just tuples with permanent names applied to each member. If you think about it that way, then tuples have always been a part of your C# experience. They've simply become a bit more extroverted. If the logic of your code requires access to multi-value constructs without the overhead of class and structure declarations, tuples might be just the thing.

About the Author

Tim Patrick has spent more than thirty years as a software architect and developer. His two most recent books on .NET development -- Start-to-Finish Visual C# 2015, and Start-to-Finish Visual Basic 2015 -- are available from http://owanipress.com. He blogs regularly at http://wellreadman.com.

comments powered by Disqus

Featured

  • AI for GitHub Collaboration? Maybe Not So Much

    No doubt GitHub Copilot has been a boon for developers, but AI might not be the best tool for collaboration, according to developers weighing in on a recent social media post from the GitHub team.

  • Visual Studio 2022 Getting VS Code 'Command Palette' Equivalent

    As any Visual Studio Code user knows, the editor's command palette is a powerful tool for getting things done quickly, without having to navigate through menus and dialogs. Now, we learn how an equivalent is coming for Microsoft's flagship Visual Studio IDE, invoked by the same familiar Ctrl+Shift+P keyboard shortcut.

  • .NET 9 Preview 3: 'I've Been Waiting 9 Years for This API!'

    Microsoft's third preview of .NET 9 sees a lot of minor tweaks and fixes with no earth-shaking new functionality, but little things can be important to individual developers.

  • Data Anomaly Detection Using a Neural Autoencoder with C#

    Dr. James McCaffrey of Microsoft Research tackles the process of examining a set of source data to find data items that are different in some way from the majority of the source items.

  • What's New for Python, Java in Visual Studio Code

    Microsoft announced March 2024 updates to its Python and Java extensions for Visual Studio Code, the open source-based, cross-platform code editor that has repeatedly been named the No. 1 tool in major development surveys.

Subscribe on YouTube