A tuple groups multiple values into a single, lightweight structure without requiring you to define a named type. Tuples are value types that you can declare inline, return from methods, and deconstruct into individual variables. Use tuples when you need a quick, temporary grouping of related values. For example, when you return multiple results from a method or store a coordinate pair.
The following example creates a tuple with named elements and accesses each element by name:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Tuples work well for short-lived groupings where defining a class, struct, or record would add unnecessary ceremony. For long-lived domain concepts or types with behavior, prefer records, classes, or structs. For a comparison of when to use each, see Choose which kind of type.
Declare and initialize tuples
Declare a tuple by listing the element types in parentheses. You can optionally name each element to make the code more readable:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}When you don't provide names, elements use default names Item1, Item2, and so on. Named elements make your code self-documenting without requiring a separate type definition.
Inferred element names
The compiler infers element names from the variable names or property names you use to initialize the tuple. This feature avoids redundancy when the names match:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Inferred names keep your code concise. If you need a different element name, specify it explicitly.
Return multiple values from a method
One of the most common uses for tuples is returning multiple values from a method. Instead of defining a class or using out parameters, return a tuple with named elements:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Named tuple elements make the return values readable at both the call site and the method signature. The caller can access each value by name without needing to remember positional order.
Deconstruct tuples
Deconstruction unpacks a tuple's elements into separate variables in a single statement. You can deconstruct tuples in several ways:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Deconstruction is especially useful when you receive a tuple from a method call and immediately need to work with its individual values.
You can deconstruct tuples directly in foreach loops, which makes iterating over collections of grouped values concise:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}When you don't need every element, use a discard (_) in place of each value you want to ignore. Use a separate _ for each discarded position:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}For more about using discards across different contexts, see Discards.
Tuple equality
You can compare tuples using == and !=. These operators compare each element in order, so two tuples are equal when all their corresponding elements are equal:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Tuple equality uses the == operator defined on each element type, which means the comparison works correctly for strings, numbers, and other types that define ==. Element names don't affect equality—only values and positions matter.
Nondestructive mutation with with
The with expression creates a copy of a tuple with one or more elements changed, leaving the original unchanged:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}This pattern is useful when you want a variation of an existing tuple without modifying the original. The with expression works the same way on tuples as it does on records.
Tuples in dictionaries and lookups
Tuples make convenient dictionary values when you need to associate a key with multiple pieces of data:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}Tuples also work as dictionary keys, giving you a composite key without defining a custom type. Because tuples implement structural equality, lookups match on the combined values of all elements:
sample · csharpdocs/csharp/fundamentals/types/snippets/tuples/Program.cs
// <BasicTuple>
var location = (Latitude: 47.6062, Longitude: -122.3321);
Console.WriteLine($"Location: {location.Latitude}, {location.Longitude}");
// Output: Location: 47.6062, -122.3321
// </BasicTuple>
BasicDeclarations();
InferredNamesExample();
MethodReturnExample();
DeconstructionExamples();
DeconstructionLoopExample();
DiscardsExample();
TupleEqualityExample();
WithExpressionExample();
DictionaryTupleExample();
TupleKeyExample();
static void BasicDeclarations()
{
// <DeclareTuples>
// Tuple with named elements
(string Name, int Age) person = ("Alice", 30);
Console.WriteLine($"{person.Name} is {person.Age} years old");
// Tuple with default element names (Item1, Item2)
(string, int) unnamed = ("Bob", 25);
Console.WriteLine($"{unnamed.Item1} is {unnamed.Item2} years old");
// Tuple declared with var and inline names
var city = (Name: "Seattle", Population: 749_256);
Console.WriteLine($"{city.Name}: population {city.Population}");
// </DeclareTuples>
}
static void InferredNamesExample()
{
// <InferredNames>
var name = "Carol";
var age = 28;
// The compiler infers element names from the variable names
var person = (name, age);
Console.WriteLine($"{person.name} is {person.age}");
// Output: Carol is 28
// </InferredNames>
}
// <MethodReturn>
static (double Minimum, double Maximum, double Average) ComputeStats(List<double> values)
{
var min = values.Min();
var max = values.Max();
var avg = values.Average();
return (min, max, avg);
}
// </MethodReturn>
static void MethodReturnExample()
{
List<double> temperatures = [72.0, 68.5, 75.3, 69.1, 71.8];
var stats = ComputeStats(temperatures);
Console.WriteLine($"Min: {stats.Minimum}, Max: {stats.Maximum}, Avg: {stats.Average:F1}");
// Output: Min: 68.5, Max: 75.3, Avg: 71.3
}
static void DeconstructionExamples()
{
// <Deconstruction>
var point = (X: 3, Y: 7);
// Deconstruct with var (infer all types)
var (x, y) = point;
Console.WriteLine($"x={x}, y={y}");
// Deconstruct with explicit types
(int px, int py) = point;
Console.WriteLine($"px={px}, py={py}");
// Deconstruct into existing variables
int a, b;
(a, b) = point;
Console.WriteLine($"a={a}, b={b}");
// Deconstruct a method return value directly
List<double> data = [10.0, 20.0, 30.0];
var (min, max, avg) = ComputeStats(data);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg}");
// </Deconstruction>
}
static void DeconstructionLoopExample()
{
// <DeconstructionLoop>
List<(string Name, int Score)> results =
[
("Alice", 92),
("Bob", 87),
("Carol", 95)
];
foreach (var (name, score) in results)
{
Console.WriteLine($"{name}: {score}");
}
// </DeconstructionLoop>
}
static void DiscardsExample()
{
// <Discards>
List<double> values = [5.0, 10.0, 15.0];
var (_, max, _) = ComputeStats(values);
Console.WriteLine($"Only need the max: {max}");
// Output: Only need the max: 15
// </Discards>
}
static void TupleEqualityExample()
{
// <TupleEquality>
var order1 = (Product: "Widget", Quantity: 5);
var order2 = (Product: "Widget", Quantity: 5);
var order3 = (Product: "Gadget", Quantity: 3);
Console.WriteLine(order1 == order2); // True
Console.WriteLine(order1 == order3); // False
// Element names don't affect equality—only values matter
var named = (X: 1, Y: 2);
var different = (A: 1, B: 2);
Console.WriteLine(named == different); // True
// </TupleEquality>
}
static void WithExpressionExample()
{
// <WithExpression>
var original = (Name: "Widget", Price: 19.99m, InStock: true);
var discounted = original with { Price = 14.99m };
Console.WriteLine($"Original: {original.Name} at {original.Price:C}");
Console.WriteLine($"Discounted: {discounted.Name} at {discounted.Price:C}");
// Output:
// Original: Widget at $19.99
// Discounted: Widget at $14.99
// </WithExpression>
}
static void DictionaryTupleExample()
{
// <DictionaryTuple>
var sizeChart = new Dictionary<string, (int Min, int Max)>
{
["Small"] = (0, 50),
["Medium"] = (51, 100),
["Large"] = (101, 200)
};
if (sizeChart.TryGetValue("Medium", out var range))
{
Console.WriteLine($"Medium: {range.Min}–{range.Max}");
}
// Output: Medium: 51–100
// </DictionaryTuple>
}
static void TupleKeyExample()
{
// <TupleKey>
var grid = new Dictionary<(int Row, int Column), string>
{
[(0, 0)] = "Origin",
[(1, 3)] = "Sensor A",
[(2, 5)] = "Sensor B"
};
var target = (Row: 1, Column: 3);
if (grid.TryGetValue(target, out var label))
{
Console.WriteLine($"({target.Row}, {target.Column}): {label}");
}
// Output: (1, 3): Sensor A
// </TupleKey>
}This pattern avoids needing a separate class for simple multi-key lookups or key-to-multiple-value mappings.
Tuples vs. anonymous types
Tuples are the preferred choice when you need a lightweight unnamed data structure. Anonymous types remain available for expression tree scenarios and for code that requires reference types, but tuples offer better performance, deconstruction support, and more flexible syntax. For more about anonymous types, see Choosing between anonymous and tuple types.
See also
- Tuple types (C# reference) for complete syntax details
- Deconstructing tuples and other types for user-defined
Deconstructmethods - Discards
- Records