How to Return Multiple Values in C# with Tuples

Returning multiple values from a method in C# traditionally required using out parameters, dedicated data transfer objects (DTOs), or the non-type-safe System.Tuple. Modern C# simplifies this process through lightweight value tuples (System.ValueTuple) and deconstruction syntax. This article covers how to define methods that return multiple values using tuples, how to deconstruct those values upon consumption, and how to implement custom deconstruct methods on your own classes.


Returning Tuples from Methods

Modern C# allows you to declare a method return type as a tuple with named elements. This provides compile-time safety and clarity without the overhead of creating a custom class or struct.

public (int Min, int Max) FindMinMax(int[] numbers)
{
    if (numbers == null || numbers.Length == 0)
    {
        throw new ArgumentException("Array cannot be empty.", nameof(numbers));
    }

    int min = numbers[0];
    int max = numbers[0];

    foreach (var n in numbers)
    {
        if (n < min) min = n;
        if (n > max) max = n;
    }

    return (min, max);
}

By naming the fields in the tuple (Min and Max), consumers of the method can access the returned values using meaningful property names:

var result = FindMinMax(new[] { 3, 1, 9, 5 });
Console.WriteLine($"Min: {result.Min}, Max: {result.Max}");

Deconstructing Tuple Returns

Instead of assigning the returned tuple to a single variable, you can deconstruct it directly into separate local variables.

1. Explicit Typing

You can declare the types of the individual variables explicitly:

(int minimum, int maximum) = FindMinMax(new[] { 10, 42, 5 });

2. Implicit Typing (var)

Using var places the type inference burden on the compiler, making the code more concise:

var (minimum, maximum) = FindMinMax(new[] { 10, 42, 5 });

3. Using Discards

If you only need a subset of the returned values, use an underscore (_) as a discard:

var (minimum, _) = FindMinMax(new[] { 10, 42, 5 });
Console.WriteLine($"Only min is needed: {minimum}");

Custom Deconstruct Methods

Deconstruction is not limited to tuples. You can enable tuple-like deconstruction on any custom class or struct by implementing a Deconstruct method. The method must:

  • Be named Deconstruct.
  • Return void.
  • Use out parameters for each value you wish to extract.
public class User
{
    public string FirstName { get; }
    public string LastName { get; }
    public string Role { get; }

    public User(string firstName, string lastName, string role)
    {
        FirstName = firstName;
        LastName = lastName;
        Role = role;
    }

    public void Deconstruct(out string firstName, out string lastName, out string role)
    {
        firstName = FirstName;
        lastName = LastName;
        role = Role;
    }
}

Once defined, instances of the class can be deconstructed using the same tuple syntax:

User user = new User("Jane", "Doe", "Administrator");

var (first, last, role) = user;
Console.WriteLine($"{first} {last} is an {role}.");

You can also provide multiple overloads of Deconstruct with different numbers of out parameters to allow callers to extract only the properties they need.