Tuplas no C# 7

Há pouco tempo saiu a versão 6 do C#. Nessa versão, as maiores mudanças ocorreram nos bastidores, foi a partir dela que o .NET passou a ser open source. Assim, pode parecer estranho falar sobre a versão 7 da linguagem, mas como os desenvolvedores não param, uma nova versão está por vir.

Desta forma, neste artigo vou falar de um dos recursos que será adicionado na linguagem que mais está me interessando, que são as tuplas (ou tuples).

C# (C Sharp) Básico
Curso de C# (C Sharp) Básico
CONHEÇA O CURSO

O que são tuplas?

Caso programe em outra linguagem, já deve se deparado com este conceito, já que tuplas não são novidades na programação.

Podemos definir as tuplas como um conjunto temporário de valores. Você pode compará-las com uma classe POCO simples, só que no lugar de criar toda a estrutura da classe, as tuplas podem ser declaradas de forma simples e rápida.

Então, no lugar de definir uma classe assim:

class Counter
{
    public int Sum {get; set;}
    public int Count {get; set;}
}

var res = new Counter { Sum = 0, Count = 0};

Isso poderia ser definido com uma tupla da seguinte maneira:

var res = (sum: 0, count: 0);

Quando utilizá-las?

Mesmo que exemplo acima mostre uma tupla inline, este recurso geralmente será utilizado como tipo de retorno de um método.

Assim como eu, você deve ter passado pela situação onde necessitava retornar mais de um valor em um método. Hoje para fazer isso temos algumas alternativas:

  • Parâmetros de saída:
public void GetCounter(IIEnumerator<int> list, out int sum, out
int count) { ... }

int sum, count;

GetCounter(list, out sum, out count);
Console.WriteLine($"Sum: {sum}, Count: {count}");

A desvantagem é que os parâmetros de saída não funcionam com métodos assíncronos.

  • A classe System.Tulpe<T, T, ...>:
public Tuple<int, int> GetCounter(IEnumerator<int> list)
{ ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Item1}, Count: {counter.Item2}");

A classe Tuple não tem a desvantagem dos parâmetros de saída, mas requer muita escrita e propriedades como Item1 e Item2 e isso tira um pouco da legibilidade do código.

  • Definir uma classe/struct ou tipo anônimo:
struct Counter { public int Sum; public int Count;}

public Counter GetCounter(IEnumerator<int> list) { ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Sum}, Count: {counter.Counter}");

Esta situação não tem as desvantagens das soluções anteriores, mas gera uma sobrecarga de código desnecessária.

Assim, no C# a melhor solução será com tuplas.

  • Utilizando tuplas:
public (int sum, int count) GetCounter(IEnumerator<int> list)
{ ... }

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Sum}, Count: {counter.Counter}");

Note que no código acima, o retorno do método foi obtido normalmente, e que a partir dele foi obtido cada um dos valores de retorno.

Acima cada propriedade da tupla só foi reconhecida porque no método elas foram especificadas:

public (int sum, int count) GetCounter(IEnumerator<int> list)
{
    int s=0, c=0;
    foreach (var v in list) { s += v; c++; }
    return (s, c);
}

Mas isso não é algo obrigatório:

public (int, int) GetCounter(IEnumerator<int> list) 
{
    int s=0, c=0;
    foreach (var v in list) { s += v; c++; }
    return (s, c);
}

Assim, as propriedades da tupla poderiam ser acessadas com as propriedades Item1, Item2, …, ItemN:

var counter = GetCounter(list);
Console.WriteLine($"Sum: {counter.Item1}, Count: {counter.Item2}");

Mas isso não é algo que eu encorajo. Então o autocomplete nem irá mostrar este tipo de opção.

Desconstrução da tupla

Como a tupla será geralmente utilizada para se retornar mais de um valor de um método, por que continuar utilizando-a depois se se obter o retorno?

Claro que poderíamos atribuir os valores de cada propriedade da tupla para uma variável e trabalhar com esses valores separadamente. Para evitar ter todo este trabalho, o C# 7 terá a opção de desconstruir a tupla:

(var sum, var count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Assim, as propriedades da tupla já serão atribuídas para as variáveis. Note que pode ser utilizado o var em cada variável, ou mesmo fora dos parentes:

var (sum, count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

O resultado será o mesmo.

Também pode ser definido o tipo primitivo:

(int sum, int count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Ou mesmo declarar as variáveis fora e definí-las nos parênteses:

int sum, count;

(sum, count) = GetCounter(list);
Console.WriteLine($"Sum: {sum}, Count: {count}");

Desta desconstrução não estará disponível apenas para as tuplas. Qualquer tipo de dado pode ser desconstruído, desde que ele implemente um método desconstrutor, com a sintaxe abaixo:

public void Deconstruct(out T1 x1, ..., out Tn xn) { ... }

Assim, quando o objeto for desconstruído, este método será chamado:

class Point
{
    public int X { get; }
    public int Y { get; }

    public Point(int x, int y) { X = x; Y = y; }

    public void Deconstruct(out int x, out int y) { x = X; y = Y; }
}

(var myX, var myY) = GetPoint(); // calls Deconstruct(out myX, out myY);

Conclusão

As tuplas são um recurso muito bem-vindo para a linguagem, facilitará o desenvolvimento e com o tempo serão muito utilizadas.

Adobe After Effects CC - Completo
Curso de Adobe After Effects CC - Completo
CONHEÇA O CURSO
Deixe seu comentário

Instrutor, nerd, cinéfilo e desenvolvedor nas horas vagas. Graduado em Ciências da Computação pela Universidade Metodista de São Paulo.