Desconstruir tuplas e outros tipos

Gorjeta

Este artigo faz parte da seção Conceitos Básicos para desenvolvedores que já conhecem pelo menos uma linguagem de programação e estão aprendendo C#. Comece com a visão geral de correspondência de padrões se os padrões forem novos para você.

Uma deconstruction atribui as partes individuais de um valor — seus componentes — a várias variáveis em uma única operação. Os componentes de uma tupla são seus elementos, expostos por posição. Outro tipo pode expor componentes definindo um método Deconstruct. Os registros posicionais, que declaram suas propriedades como parâmetros semelhantes a construtores, obtêm automaticamente um método Deconstruct.

Desconstruir tuplas

Suponha que um método retorne uma tupla com dados da cidade. Você pode ler cada componente um de cada vez:

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

Uma desestruturação atribui esses componentes em uma etapa:

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

Você também pode permitir que o C# infera os tipos de variável:

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

Uma desconstrução pode misturar variáveis existentes, variáveis recém-declaradas e descartes em uma atribuição:

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

Escolha a forma que torna o código mais fácil de ler. Um único var antes dos parênteses geralmente é a forma inferida mais clara. Você também pode misturar tipos explícitos e var dentro dos parênteses, mas essa forma geralmente é mais difícil de verificar. Se você precisar apenas de alguns valores, use discards em vez de ignorar posições.

Ignorar valores desnecessários com descartes

Cada valor produzido deve se alinhar com uma posição à esquerda da instrução de atribuição. Quando você não precisar de uma ou mais posições, use _ como descarte:

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

Aqui, a tupla retorna o nome da cidade, dois anos e dois valores populacionais. A desconstrução mantém apenas os valores de população porque o cálculo usa apenas esses componentes.

Desconstruir tipos definidos pelo usuário

Uma classe, struct ou interface pode dar suporte à desconstrução declarando um método Deconstruct. Cada componente se torna um out parâmetro de saída, o que permite que o método atribua um valor de volta à variável do chamador sem retorná-lo. Como cada componente é retornado por meio de um out parâmetro, o próprio método retorna void:

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

Em seguida, você pode desestruturar uma instância diretamente:

var (firstName, middleName, lastName) = passenger;

Um tipo pode fornecer várias Deconstruct sobrecargas de método com arity diferente — o número de out parâmetros que o método declara — para que os chamadores possam escolher quantos componentes recuperar:

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;
}

Duas sobrecargas com o mesmo número de out parâmetros são ambíguas. O compilador relata um erro para a chamada ambígua, portanto, distinga as sobrecargas pela aridade, não apenas pelos tipos de parâmetro.

Os descartes também funcionam com desconstrução definida pelo usuário. Para obter mais informações sobre descartes em geral, consulte Descartes e o padrão de descarte:

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

Desconstruir registros

Um posicional record ou record struct declara suas propriedades como parâmetros na própria declaração de tipo, semelhante a um construtor. O compilador gera um método Deconstruct para você, com parâmetros out correspondentes a esses parâmetros posicionais:

var (city, highTempC, lowTempC) = forecast;

Somente os parâmetros posicionais participam da decomposição gerada. Propriedades adicionais que você declara em outro lugar no registro não são adicionadas automaticamente.

Desconstrua tipos que você não possui

Se você não puder modificar um tipo, ainda poderá dar suporte à desconstrução escrevendo um método de extensão — um método estático que adiciona um método Deconstruct a um tipo que você não possui, como se fosse um membro desse tipo. Depois de adicionar o método, qualquer valor Uri pode usar a sintaxe de desconstrução:

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;
    }
}

A mesma regra de ambiguidade se aplica aqui: dois métodos de extensão Deconstruct com a mesma aridade são ambíguos. A ambiguidade também pode surgir entre um método de instância Deconstruct e um método de extensão da mesma aridade. Em ambos os casos, o compilador relata um erro para a chamada ambígua.

Desconstrução integrada em tipos de sistema

Alguns tipos de sistema já definem o método Deconstruct, usando o mesmo mecanismo que você usaria para seus próprios tipos. Por exemplo, System.Collections.Generic.KeyValuePair<TKey,TValue> dá suporte à desconstrução, o que torna a iteração de dicionário concisa:

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

Desconstrução e pattern matching

Um Deconstruct método também habilita padrões posicionais para esse tipo. Um padrão posicional testa e desconstrói um valor em uma etapa, usando a mesma sintaxe parêntese que uma desconstrução: person is ("Alice", 30) corresponde a um Person cujos componentes desconstruídos são iguais a esses valores. Isso é diferente de um padrão de propriedade, que testa propriedades nomeadas diretamente, como person is { Name: "Alice", Age: 30 }. Os padrões de propriedade geralmente são mais claros para formas de objeto porque os nomes de membro explicam o teste. Os padrões posicionais são mais fortes quando a ordem já carrega o significado, como com tuplas ou outros valores ordenados pequenos.

Consulte também