Edit

Deconstruct tuples and other types

Tip

This article is part of the Fundamentals section for developers who already know at least one programming language and are learning C#. Start with the pattern matching overview if patterns are new to you.

A deconstruction assigns the individual parts of a value — its components — to multiple variables in a single operation. A tuple's components are its elements, exposed by position. Another type can expose components by defining a Deconstruct method. Positional records, which declare their properties as constructor-like parameters, get a Deconstruct method automatically.

Deconstruct tuples

Suppose a method returns a tuple with city data. You can read each component one at a time:

var cityData = QueryCityData("New York City");
var city = cityData.City;
var population = cityData.Population;
var area = cityData.Area;

A deconstruction assigns those components in one step:

(string city, int population, double area) = QueryCityData("New York City");

You can also let C# infer the variable types:

var (city, population, area) = QueryCityData("New York City");

A deconstruction can mix existing variables, newly declared variables, and discards in one assignment:

(city, var population, _) = QueryCityData("New York City");

Choose the form that makes the code easiest to read. A single var before the parentheses is often the clearest inferred form. You can also mix explicit types and var inside the parentheses, but that form is usually harder to scan. If you need only some values, use discards instead of omitting positions.

Ignore unneeded values with discards

Every produced value must line up with a position on the left side of the assignment. When you do not need one or more positions, use _ as a discard:

var (_, _, population1960, _, population2010) = QueryPopulationDataForYears(
    "New York City", 1960, 2010);

Here, the tuple returns the city name, two years, and two population values. The deconstruction keeps only the population values because the calculation uses only those components.

Deconstruct user-defined types

A class, struct, or interface can support deconstruction by declaring a Deconstruct method. Each component becomes an out parameter, which lets the method assign a value back to the caller's variable without returning it. Because every component is returned through an out parameter, the method itself returns void:

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

You can then deconstruct an instance directly:

var (firstName, middleName, lastName) = passenger;

A type can provide multiple Deconstruct overloads with different arity — the number of out parameters the method declares — so callers can choose how many components to retrieve:

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

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

public void Deconstruct(out string firstName, out string lastName, out string city, out string state)
{
    firstName = FirstName;
    lastName = LastName;
    city = City;
    state = State;
}

Two overloads with the same number of out parameters are ambiguous. The compiler reports an error for the ambiguous call, so distinguish overloads by arity, not only by parameter types.

Discards work with user-defined deconstruction too. For more on discards in general, see Discards and the discard pattern:

var (firstName, _, city, _) = passenger;

Deconstruct records

A positional record or record struct declares its properties as parameters on the type declaration itself, similar to a constructor. The compiler generates a Deconstruct method for you, with out parameters matching those positional parameters:

var (city, highTempC, lowTempC) = forecast;

Only the positional parameters participate in that generated deconstruction. Additional properties you declare elsewhere on the record are not added automatically.

Deconstruct types you don't own

If you cannot modify a type, you can still support deconstruction by writing an extension method — a static method that adds a Deconstruct method to a type you don't own, as if it were a member of that type. After you add the method, any Uri value can use deconstruction syntax:

static class UriExtensions
{
    public static void Deconstruct(this Uri uri, out string scheme, out string host, out int port)
    {
        scheme = uri.Scheme;
        host = uri.Host;
        port = uri.Port;
    }
}

The same ambiguity rule applies here: two extension Deconstruct methods with the same arity are ambiguous. Ambiguity can also arise between an instance Deconstruct method and an extension method of the same arity. In either case, the compiler reports an error for the ambiguous call.

Built-in deconstruction on system types

Some system types already define a Deconstruct method, using the same mechanism you'd use for your own types. For example, System.Collections.Generic.KeyValuePair<TKey,TValue> supports deconstruction, which makes dictionary iteration concise:

foreach (var (repo, commitCount) in repoCommitCounts)
{
    Console.WriteLine($"{repo} had {commitCount:N0} commits in this snapshot.");
}

Deconstruction and pattern matching

A Deconstruct method also enables positional patterns for that type. A positional pattern tests and deconstructs a value in one step, using the same parenthesized syntax as a deconstruction: person is ("Alice", 30) matches a Person whose deconstructed components equal those values. This is different from a property pattern, which tests named properties directly, such as person is { Name: "Alice", Age: 30 }. Property patterns are usually clearer for object shapes because member names explain the test. Positional patterns are strongest when order already carries the meaning, such as with tuples or other small ordered values.

See also