ZetCode

C# tuple

last modified November 6, 2026

In this article we show how to work with a tuple type in C#.

A tuple is a grouping of ordered values. The values can be of different types and may optionally be named. Tuples can either be reference types (backed by System.Tuple) or value types (backed by System.ValueTuple).

Since C# 7, tuples are part of the language syntax. The syntax uses round brackets to define and work with tuples, which are backed by System.ValueTuple. In this article we focus on this syntax. Tuples are handy for grouping a few related values without defining a new type; for data that is passed around widely, a record or a class is usually a better choice.

C# tuple simple example

In the following example, we create a new tuple using the old and the new way.

Program.cs
var u1 = Tuple.Create("John", "Doe", "gardener");
Console.WriteLine($"{u1.Item1} {u1.Item2} is a {u1.Item3}");

var u2 = ("Roger", "Roe", "driver");
Console.WriteLine($"{u2.Item1} {u2.Item2} is a {u2.Item3}");

Console.WriteLine(u1.GetType());
Console.WriteLine(u2.GetType());

We create two tuples and print their contents to the console.

var u1 = Tuple.Create("John", "Doe", "gardener");
Console.WriteLine($"{u1.Item1} {u1.Item2} is a {u1.Item3}");

This is the old way of creating tuples. A new instance of System.Tuple is created. The members of the tuple are accessed with ItemN properties. This tuple is a reference type and its members are read-only.

var u2 = ("Roger", "Roe", "driver");
Console.WriteLine($"{u2.Item1} {u2.Item2} is a {u2.Item3}");

Here is the modern way of creating a tuple. This tuple is a value type. We access the tuple fields with Item1, Item2, and Item3; however, it is possible to name the fields.

Console.WriteLine(u1.GetType());
Console.WriteLine(u2.GetType());

We print the underlying types of the two tuples.

$ dotnet run
John Doe is a gardener
Roger Roe is a driver
System.Tuple`3[System.String,System.String,System.String]
System.ValueTuple`3[System.String,System.String,System.String]

C# tuple field names

We can name our tuple fields.

Program.cs
var u1 = (Name: "John Doe", Age: 34);
Console.WriteLine($"{u1.Name} is {u1.Age} years old");

(string Name, int Age) u2 = ("Roger Roe", 55);
Console.WriteLine($"{u2.Name} is {u2.Age} years old");

var name = "Jane Doe";
var age = 26;

var u3 = (name, age);
Console.WriteLine($"{u3.name} is {u3.age} years old");

Console.WriteLine(u3.Item1);
Console.WriteLine(u1);

The example shows three different ways to name tuple fields.

var u1 = (Name: "John Doe", Age: 34);

The field name is separated from its value with a colon character. The fields are separated with commas.

Console.WriteLine($"{u1.Name} is {u1.Age} years old");

Here we refer to the tuple fields by their names.

(string Name, int Age) u2 = ("Roger Roe", 55);

We can specify the names and the types of the fields inside a pair of round brackets before the variable name.

var name = "Jane Doe";
var age = 26;

var u3 = (name, age);

When a tuple is created from existing variables, the compiler infers both the types and the names of the fields from the variables. This feature is called inferred tuple element names.

Console.WriteLine(u3.Item1);
Console.WriteLine(u1);

The names exist only at compile time; the ItemN fields are still available. The ToString method of a tuple prints its values in round brackets.

$ dotnet run
John Doe is 34 years old
Roger Roe is 55 years old
Jane Doe is 26 years old
Jane Doe
(John Doe, 34)

C# value tuples are mutable

Unlike System.Tuple, the fields of a value tuple can be modified. Since it is a value type, assignment copies the whole tuple.

Program.cs
var p = (X: 1, Y: 2);
p.X = 10;
Console.WriteLine(p);

var q = p;
q.Y = 20;

Console.WriteLine(p);
Console.WriteLine(q);

We change a field of a tuple and copy the tuple to another variable.

var q = p;
q.Y = 20;

The q variable gets a copy of p. Modifying q does not affect the original tuple.

$ dotnet run
(10, 2)
(10, 2)
(10, 20)

C# tuple swap values

With tuples, we can easily swap values of two variables.

Program.cs
var (x, y) = (11, 2);

Console.WriteLine($"x: {x}");
Console.WriteLine($"y: {y}");

(y, x) = (x, y);

Console.WriteLine($"x: {x}");
Console.WriteLine($"y: {y}");

We define and initialize two variables x and y. Later we swap their values in one line.

(y, x) = (x, y);

The right-hand tuple is created first from the current values; then it is deconstructed into the variables on the left. No temporary variable is needed.

$ dotnet run
x: 11
y: 2
x: 2
y: 11

C# tuple deconstruction

We can unpack all the items in a tuple in a single operation. This is called tuple deconstruction.

Program.cs
var (name1, age1) = ("John Doe", 34);
Console.WriteLine($"{name1} is {age1} years old");

(string name2, int age2) = ("Roger Roe", 55);
Console.WriteLine($"{name2} is {age2} years old");

string name3;
int age3;

(name3, age3) = ("Jane Doe", 26);
Console.WriteLine($"{name3} is {age3} years old");

var (name4, _, occupation4) = ("Paul Smith", 41, "teacher");
Console.WriteLine($"{name4} is a {occupation4}");

The example shows how to deconstruct tuples.

var (name1, age1) = ("John Doe", 34);

The tuple field values are stored in the name1 and age1 variables. We declare and assign the variables in one step.

(string name2, int age2) = ("Roger Roe", 55);

Here we explicitly specify the variable types.

string name3;
int age3;

(name3, age3) = ("Jane Doe", 26);

We can also deconstruct a tuple into existing variables.

var (name4, _, occupation4) = ("Paul Smith", 41, "teacher");

The underscore is a discard; it ignores a value we are not interested in.

$ dotnet run
John Doe is 34 years old
Roger Roe is 55 years old
Jane Doe is 26 years old
Paul Smith is a teacher

C# nested tuples

It is possible to nest tuples into other tuples.

Program.cs
var data = ((1, 2, 3), (3, 4, 5), (6, 7, 8));
Console.WriteLine(data);

Console.WriteLine(data.Item1);
Console.WriteLine(data.Item2);
Console.WriteLine(data.Item3);

Console.WriteLine(data.Item1.Item1);
Console.WriteLine(data.Item2.Item3);

We have a tuple which has three nested tuples consisting of three values.

Console.WriteLine(data.Item1);

Here we get the first nested tuple: (1, 2, 3).

Console.WriteLine(data.Item1.Item1);
Console.WriteLine(data.Item2.Item3);

Here we get the first element of the first nested tuple and the third element of the second nested tuple.

$ dotnet run
((1, 2, 3), (3, 4, 5), (6, 7, 8))
(1, 2, 3)
(3, 4, 5)
(6, 7, 8)
1
5

C# tuple equality

Since C# 7.3, tuples support the == and != operators. The tuples are compared element by element, in order.

Program.cs
var a = (Name: "John", Age: 34);
var b = (Name: "John", Age: 34);
var c = (First: "John", Years: 34);

Console.WriteLine(a == b);
Console.WriteLine(a == c);
Console.WriteLine(a != ("John", 35));
Console.WriteLine(a.Equals(b));

We compare tuples with the equality operators and the Equals method.

Console.WriteLine(a == c);

The field names do not take part in the comparison; only the values matter. The a and c tuples are therefore equal.

$ dotnet run
True
True
True
True

C# tuple as function parameter

Tuples can be function parameters.

Program.cs
ShowInfo(("John", "Doe", "programmer"));
ShowInfo(("Roger", "Roe", "driver"));
ShowInfo(("Jane", "Doe", "teacher"));

void ShowInfo((string FirstName, string LastName, string Occupation) u)
{
    Console.WriteLine($"{u.FirstName} {u.LastName} is a {u.Occupation}");
}

We pass tuples to the ShowInfo function, which builds and prints a message from their fields.

void ShowInfo((string FirstName, string LastName, string Occupation) u)

The parameter type lists the types and names of the tuple fields.

$ dotnet run
John Doe is a programmer
Roger Roe is a driver
Jane Doe is a teacher

C# tuple as return value

Tuples are often used to return multiple values from a function. This replaces out parameters or small helper classes.

Program.cs
int[] vals = [4, -2, 9, 0, 13, 7];

var stats = GetStats(vals);
Console.WriteLine($"min: {stats.Min}, max: {stats.Max}, avg: {stats.Avg:F2}");

var (min, max, _) = GetStats([1, 2, 3]);
Console.WriteLine($"min: {min}, max: {max}");

(int Min, int Max, double Avg) GetStats(int[] data)
{
    return (data.Min(), data.Max(), data.Average());
}

The GetStats function computes the minimum, maximum, and average of an array of integers.

(int Min, int Max, double Avg) GetStats(int[] data)
{
    return (data.Min(), data.Max(), data.Average());
}

The return type is a tuple having three named fields.

var (min, max, _) = GetStats([1, 2, 3]);

The returned tuple can be deconstructed directly; we discard the average.

$ dotnet run
min: -2, max: 13, avg: 5.17
min: 1, max: 3

C# tuples in collections

Tuples can be stored in collections and produced by LINQ queries.

Program.cs
var users = new List<(string Name, string Occupation, int Age)>
{
    ("John Doe", "gardener", 34),
    ("Roger Roe", "driver", 55),
    ("Jane Doe", "teacher", 26),
    ("Lucy Smith", "teacher", 41),
};

foreach (var (name, occupation, age) in users.Where(u => u.Age > 30))
{
    Console.WriteLine($"{name}, {occupation}, {age}");
}

var distances = new Dictionary<(string, string), int>
{
    [("Bratislava", "Vienna")] = 80,
    [("Bratislava", "Budapest")] = 200,
};

Console.WriteLine(distances[("Bratislava", "Vienna")]);

foreach (var g in users.GroupBy(u => u.Occupation)
                       .Select(g => (Occupation: g.Key, Count: g.Count())))
{
    Console.WriteLine($"{g.Occupation}: {g.Count}");
}

We have a list of users represented by tuples.

foreach (var (name, occupation, age) in users.Where(u => u.Age > 30))

The foreach statement can deconstruct each tuple into variables.

var distances = new Dictionary<(string, string), int>

Since value tuples implement equality and hash codes, they can serve as composite dictionary keys.

foreach (var g in users.GroupBy(u => u.Occupation)
                       .Select(g => (Occupation: g.Key, Count: g.Count())))

A LINQ projection creates a tuple with the occupation and the number of users in each group.

$ dotnet run
John Doe, gardener, 34
Roger Roe, driver, 55
Lucy Smith, teacher, 41
80
gardener: 1
driver: 1
teacher: 2

C# tuple & pattern matching

A pattern is a syntactic form that can be used to express the shape of data against which incoming data is compared. Tuple patterns allow us to match several values at once.

Program.cs
(string, string)[] games =
[
    ("rock", "scissors"),
    ("paper", "rock"),
    ("scissors", "rock"),
    ("paper", "paper"),
];

foreach (var (p1, p2) in games)
{
    Console.WriteLine($"player 1 has {p1}, player 2 has {p2}");
    Console.WriteLine(RockPaperScissors(p1, p2));
}

string RockPaperScissors(string player1, string player2) => (player1, player2) switch
{
    ("rock", "paper") => "Rock is covered by paper. Player 2 wins.",
    ("rock", "scissors") => "Rock breaks scissors. Player 1 wins.",
    ("paper", "rock") => "Paper covers rock. Player 1 wins.",
    ("paper", "scissors") => "Paper is cut by scissors. Player 2 wins.",
    ("scissors", "rock") => "Scissors are broken by rock. Player 2 wins.",
    ("scissors", "paper") => "Scissors cut paper. Player 1 wins.",
    (_, _) => "Tie."
};

This is a simple skeleton of a rock-paper-scissors game. We evaluate a few fixed games.

string RockPaperScissors(string player1, string player2) => (player1, player2) switch
{
    ("rock", "paper") => "Rock is covered by paper. Player 2 wins.",
    ("rock", "scissors") => "Rock breaks scissors. Player 1 wins.",
    ("paper", "rock") => "Paper covers rock. Player 1 wins.",
    ("paper", "scissors") => "Paper is cut by scissors. Player 2 wins.",
    ("scissors", "rock") => "Scissors are broken by rock. Player 2 wins.",
    ("scissors", "paper") => "Scissors cut paper. Player 1 wins.",
    (_, _) => "Tie."
};

We use tuple patterns in a switch expression. The tuples represent choices of the two players. Each of the switch expression arms represents a possible scenario in the game. The last arm with two discards matches any remaining combination, which in this game is a tie.

$ dotnet run
player 1 has rock, player 2 has scissors
Rock breaks scissors. Player 1 wins.
player 1 has paper, player 2 has rock
Paper covers rock. Player 1 wins.
player 1 has scissors, player 2 has rock
Scissors are broken by rock. Player 2 wins.
player 1 has paper, player 2 has paper
Tie.

Source

Tuple types - language reference

In this article we have worked with tuples in C#.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.