Browse C#

C# Tuples: Group Values Without Creating a Type

Tuples let you return multiple values from a method without a custom class or out parameters. Here's the syntax, when to use them, and the difference between Tuple and ValueTuple.

There's also a video on this topic using different examples, and the two complement each other.

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)
TypeReference typeValue type
AllocationHeapGenerally stack
Field accessItem1, Item2Named fields
MutabilityImmutableMutable
SyntaxTuple.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.

Related reading

  • .NET

    Modern .NET Explained: A Beginner-Friendly Guide Video

    What .NET actually is, how it evolved from .NET Framework to a unified modern platform, and what you need to know to get started without any confusion.

    5 min
  • C# 15

    C# 15: Collection Expression Arguments Video

    C# 15 extends collection expressions with the with keyword, letting you pass constructor arguments like capacity or a comparer directly inside the expression.

    2 min
  • SOLID

    SOLID Principles in C#: A Beginner-Friendly Guide Video

    All software starts simple. SOLID principles are how you keep it from collapsing under its own weight as it grows. This is a practical walkthrough of all the five principles.

    8 min