A tuple bundles a small, fixed number of values into one value without declaring a class for it. ("Ana", 31) is a tuple of a string and an int. Tuples are the idiomatic way to return two or three things from a method and to hold short-lived pairs inside a method.
Creating a tuple
The type is written with parentheses too: (string, int) is a tuple type whose first element is a string and second an int.
Output:
Ana is 31
(3, 4)
179.70
(Ana, 32)
Without names, the elements are Item1, Item2, Item3 and so on. These tuples are System.ValueTuple structs, so assigning one to another variable copies the values, and changing the copy leaves the original alone.
Named elements
Item1 says nothing about what the value is. Give the elements names, either in the type or in the literal:
Output:
Ana, 31
Desk lamp costs 24.99
Ana
Ana
Names are a compile-time convenience. At run time both tuples above are plain ValueTuple<string, int> values, which is why assigning person to a tuple with different names compiles: only the element types and their order must match.
C# 7.1 and later also infer names from the variables in the literal, so var t = (name, age); gives you t.name and t.age without writing them twice. In C# 7.0 those elements are only Item1 and Item2.
Returning multiple values from a method
This is the main reason tuples were added to the language. Before them the options were out parameters, a one-off class, or System.Tuple with its anonymous Item1.
Output:
Lowest 60, highest 95
Average 78.75
The element names in the return type become the names the caller sees. Compare the same method with out parameters:
static void Stats(int[] scores, out int min, out int max, out double average) { ... }
Stats(scores, out int min, out int max, out double avg);
out parameters remain the convention for the TryParse shape, where a bool says whether it worked and the value comes back alongside it. For "compute several things and return them", a tuple reads better. See ref and out.
Deconstruction
Deconstruction unpacks a tuple into separate variables in one statement:
Output:
Lima 24.5
Lima
24.5
2 1
The variable names in var (city, temp) are yours; they do not have to match the tuple's element names, because deconstruction goes by position. The swap (a, b) = (b, a) builds a tuple from the old values first and then assigns, so neither value is lost.
Your own classes can support deconstruction by declaring a Deconstruct method with out parameters. From C# 7 with .NET Core 2.0 or later, KeyValuePair has one too, so a dictionary loop can unpack each entry in its header:
class Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}
var (x, y) = new Point(3, 4);
foreach (var (name, score) in scoresByName) // Dictionary<string, int>
{
Console.WriteLine($"{name}: {score}");
}
The same foreach (var (a, b) in listOfTuples) form works for a list of tuples. Taking the whole tuple as one loop variable and reading its named elements, as the next section does, works just as well.
Tuples in lists, LINQ and dictionaries
A list of named tuples is a light way to hold rows of data inside a method:
Output:
Chloe 95
Ana 88
Ben 72
(Ana, B), (Chloe, A)
Ben
False
ValueTuple implements Equals and GetHashCode element by element, so (2, 7) built in two different places finds the same dictionary entry. That makes a tuple the simplest composite key: no class, no hand-written hash function, no string concatenation like row + ":" + col.
Tuple equality
Equals compares element by element and has worked since tuples were introduced:
Output:
True
False
C# 7.3 added == and != for tuples, which compile to the same element by element comparison and also ignore names:
if ((order.Status, order.Paid) == ("shipped", true)) { ... }
Console.WriteLine((1, "x") == (1, "x")); // True
System.Tuple vs ValueTuple
.NET 4 introduced System.Tuple, a class created with Tuple.Create. It is still in the framework and still shows up in older code and APIs, which is why searches for "C# tuple" often land on it.
ValueTuple (C# 7) | System.Tuple (.NET 4) | |
|---|---|---|
| Syntax | (1, "a"), (int, string) | Tuple.Create(1, "a"), Tuple<int, string> |
| Kind | struct (value type) | class (reference type) |
| Element names | Yes | No, only Item1, Item2 |
| Mutable | Yes, elements are fields | No, read-only properties |
| Deconstruction | Yes | Yes, through extension methods |
Output:
Ben 25
(Ben, 25)
ToValueTuple() and ToTuple() convert between the two. Prefer value tuples in new code: they allocate nothing on the heap and they can carry names.
When to use a class or record instead
Tuples are best when the grouping is local and obvious: a method returns two numbers, a LINQ query carries a pair to the next step, a dictionary needs a two-part key. Switch to a named type when:
- The same shape appears in several public method signatures.
(string, string, int)in five places is a class that has not been written yet. - The values need behavior, validation, or more than three or four elements.
- The data crosses a boundary such as serialization. The names do not exist at run time, so Newtonsoft.Json writes
Item1andItem2, andSystem.Text.Jsonwrites{}because tuple elements are fields, which it skips by default.
C# 9 records give you most of a tuple's convenience with a real name. record Score(string Name, int Points); has value equality, a readable ToString and deconstruction built in. See records.
Common mistakes
- Expecting names to survive at run time. Reflection and serializers see
Item1,Item2(or nothing at all). - Mutating a copy. Tuples are structs:
var t2 = t1; t2.Item1 = 5;leavest1unchanged. - Tuples with many elements. Past three or four, a named type reads better.
- Mixing up deconstruction order. It is positional: with a
(Name, Age)tuple,var (age, name) = person;puts the name inage, and becausevarinfers the types, nothing warns you.
Frequently Asked Questions
How do I return multiple values from a method in C#?
Return a tuple: declare the return type as (int Min, int Max) and return (lowest, highest);. The caller reads result.Min and result.Max, or deconstructs with var (min, max) = MinMax(data);. The alternatives are out parameters, which suit the TryParse pattern, or a small class or record when the result has a meaning of its own.
What is a named tuple in C#?
A tuple whose elements have names instead of Item1, Item2: (string Name, int Age) person = ("Ana", 31); lets you write person.Name. The names exist only at compile time; underneath it is still a ValueTuple<string, int>, and Item1 keeps working.
What is the difference between Tuple and ValueTuple in C#?
System.Tuple (from .NET 4) is a class: allocated on the heap, immutable, and its elements are only ever Item1, Item2. System.ValueTuple (C# 7) is a struct with mutable fields, supports element names and the (a, b) syntax, and is what every tuple literal creates. Use value tuples in new code.
How does tuple deconstruction work in C#?
var (name, age) = person; declares two variables and assigns the tuple's elements to them in order. You can also assign to existing variables with (a, b) = (b, a), which swaps them, and skip elements with the discard _: var (_, age) = person;.
Can I compare tuples with == in C#?
Since C# 7.3, == and != compare tuples element by element: (1, "a") == (1, "a") is true. Names are ignored; only positions and values matter. Before C# 7.3, use Equals, which value tuples implement the same way.