Syntax
(type1 name1, type2 name2) tuple = (value1, value2);Examples
Basic Tuples
Creating and accessing a tuple with named elements.
(string Name, int Age) person = ("Fola", 21);
Console.WriteLine(person.Name); // Fola
Console.WriteLine(person.Age); // 21
// Without names, elements are accessed as Item1, Item2, etc.
var point = (3, 4);
Console.WriteLine(point.Item1); // 3Returning Multiple Values from a Method
A very common use of tuples: avoiding an out parameter or a dedicated wrapper class.
(int Min, int Max) GetMinMax(List<int> numbers)
{
return (numbers.Min(), numbers.Max());
}
var result = GetMinMax(new List<int> { 4, 1, 9, 2, 7 });
Console.WriteLine($"Min: {result.Min}, Max: {result.Max}");Deconstruction
Unpacking a tuple directly into separate named variables.
(int Min, int Max) GetMinMax(List<int> numbers) => (numbers.Min(), numbers.Max());
var (low, high) = GetMinMax(new List<int> { 4, 1, 9, 2, 7 });
Console.WriteLine($"Low: {low}, High: {high}");Deconstructing Custom Types
Any class or record can support deconstruction by defining a Deconstruct method (records get this automatically).
public class Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public void Deconstruct(out int x, out int y)
{
x = X;
y = Y;
}
}
Point p = new Point(3, 4);
var (x, y) = p; // uses the custom Deconstruct method
Console.WriteLine($"x={x}, y={y}");Best practices
- Use named tuple elements ((string Name, int Age)) instead of the default Item1/Item2 for significantly better readability at call sites
- Use tuples for small, temporary, internal groupings - switch to a record or class once the data has real identity or is passed around extensively
- Use deconstruction (var (a, b) = tuple;) when you plan to use the individual values by name, rather than repeatedly writing result.Item1
- Add a Deconstruct method to your own classes when destructuring them into separate variables would be a common, natural usage pattern
At a glance
- Purpose
- Applications on the .NET platform
- File extension
- .cs
- Runs in
- .NET runtime
- Usually used with
- .NET SDK and libraries
Specifications & further reading
Related C# documentation
Delegates
A delegate is a type-safe reference to a method - essentially a variable that can hold a method and be invoked like one. Delegates enable passing behavior around as data, forming the foundation for events, callbacks, and LINQ. C# provides built-in generic delegate types, Func<> and Action<>, that cover the vast majority of use cases without needing to declare a custom delegate type.Events
An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.Extension Methods
Extension methods let you add new methods to an existing type - including types you don't own, like built-in .NET types or third-party classes - without modifying its source code or creating a subclass. They're defined as static methods in a static class, with the first parameter prefixed with this to indicate which type they extend. LINQ's Where(), Select(), and friends are themselves implemented as extension methods on IEnumerable<T>.Nullable Reference Types & Null-Coalescing
Since C# 8, nullable reference types let the compiler track and warn about potential null reference issues at compile time, even for reference types like string and custom classes (which were always implicitly nullable before). A type like string means 'never null' under this feature, while string? explicitly allows null. The null-coalescing operator (??) and null-conditional operator (?.) provide concise syntax for safely working with potentially-null values.
A delegate is a type-safe reference to a method - essentially a variable that can hold a method and be invoked like one. Delegates enable passing behavior around as data, forming the foundation for events, callbacks, and LINQ. C# provides built-in generic delegate types, Func<> and Action<>, that cover the vast majority of use cases without needing to declare a custom delegate type.Events
An event is a special kind of delegate field that provides a publish-subscribe pattern: the declaring class can raise (invoke) the event, but external code can only subscribe (+=) or unsubscribe (-=) - it cannot invoke the event directly or overwrite existing subscribers. This makes events the standard, safe way to notify other parts of a program that something has happened, like a button being clicked or a value changing.Extension Methods
Extension methods let you add new methods to an existing type - including types you don't own, like built-in .NET types or third-party classes - without modifying its source code or creating a subclass. They're defined as static methods in a static class, with the first parameter prefixed with this to indicate which type they extend. LINQ's Where(), Select(), and friends are themselves implemented as extension methods on IEnumerable<T>.Nullable Reference Types & Null-Coalescing
Since C# 8, nullable reference types let the compiler track and warn about potential null reference issues at compile time, even for reference types like string and custom classes (which were always implicitly nullable before). A type like string means 'never null' under this feature, while string? explicitly allows null. The null-coalescing operator (??) and null-conditional operator (?.) provide concise syntax for safely working with potentially-null values.