Codectionary / Developer documentation / C#

Tuples & Deconstruction

C# tuples let you group multiple values together without defining a dedicated class or struct, ideal for lightweight, temporary groupings like returning multiple values from a method. Modern C# (7+) supports named tuple elements for readability, and deconstruction syntax lets you unpack a tuple (or any type that supports it) into separate variables in one statement.

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);  // 3

Returning 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.