Sometimes a method needs to return more than one value. The usual options are to create a custom type or use the out parameter. Both of those options work, however both also have their disadvantages. Let’s take a look at the issues and then see how tuples can help you solve them.
The problem
Imagine a method that analyzes a block of text and produces two pieces of information: how many words it contains and how many characters. In other words, the return type needs to contain two pieces of data.
Option 1: custom type. You define a class or record with two properties, give it its own file, and return it from the method. For a public API this is the right call, because named types are reusable. However, if you don’t require reusability such as for a private helper, then creating a dedicated custom type is overkill.
Option 2: out parameters. Once again this works, but the downside is that the method signature becomes harder to read and the caller has to declare separate variables upfront, because the values are returned through the parameters rather than a return statement. It’s also error-prone - the caller has to manage all those variable declarations.
Option 3: a tuple. You do not need to create a dedicated custom type, and you don’t need to clutter the method signature with extra parameters. You simply return multiple values (in this case two values) together.
Tuple syntax
The modern shorthand (value tuple syntax) uses parentheses:
// Unnamed fields - valid but not recommended
(int, int) metrics = (1200, 6800);
// Named fields - recommended
(int wordCount, int charCount) metrics = (1200, 6800);
Named fields make the tuple more readable and intuitive to work with. Unnamed fields leave you with Item1, Item2 (and so on), which tells you nothing about what the values mean.
A realistic example
Here’s the full method returning a named tuple:
(int wordCount, int charCount) GetTextMetrics(string content)
{
int words = content.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
int chars = content.Replace(" ", "").Length;
return (words, chars);
}
Accessing the result - not recommended:
var metrics = GetTextMetrics(content);
var words = metrics.Item1; // Unclear
var chars = metrics.Item2; // Unclear
Item1 and Item2 are the fallback names when fields aren’t named. They’re technically accessible, but tell you nothing about the values. This way of accessing results is not recommended!
Accessing named fields - recommended:
var metrics = GetTextMetrics(content);
var words = metrics.wordCount;
var chars = metrics.charCount;
Using deconstruction - also recommended:
var (wordCount, charCount) = GetTextMetrics(content);
Deconstruction unpacks the tuple directly into variables in a single line. Use it when you need the values immediately and you don’t need to pass it around.
Tuple vs ValueTuple
If you’ve come across Tuple<T1, T2> in older code - don’t be surprised! This is the legacy reference type. The tuple syntax covered in this article is what’s known as a ValueTuple, which is different in a few important ways:
| Tuple (legacy) | ValueTuple (modern) | |
|---|---|---|
| Type | Reference type | Value type |
| Allocation | Heap | Generally stack |
| Field access | Item1, Item2… | Named fields |
| Mutability | Immutable | Mutable |
| Syntax | Tuple.Create(1, 2) | (1, 2) |
For new code, stick to the value tuple syntax. The parentheses form is concise, it supports named fields and very importantly you avoid heap allocation for superior performance versus the legacy Tuple<T> class.
The takeaway
Use tuples when you need to return a few loosely related values from a method and creating a dedicated type seems like an overkill. Always name your tuple fields as leaving them unnamed means you’re stuck with Item1 and Item2, which tell you nothing. If you need the tuple values right away, deconstruction is the cleaner option. If the values are closely related and you’ll be reusing them across the codebase, then a custom type is a better option.