Descartar - Fundamentos do C#

Os descartes são variáveis de espaço reservado que não são intencionalmente usadas no código do aplicativo. As devoluções são equivalentes a variáveis não atribuídas; eles não têm um valor. Um descarte comunica a intenção ao compilador e a outras pessoas que leem seu código: você pretendia ignorar o resultado de uma expressão. Você pode querer ignorar o resultado de uma expressão, um ou mais membros de uma expressão de tupla, um out parâmetro para um método ou o destino de uma expressão de correspondência de padrão.

Os descartes deixam clara a intenção do seu código. Um descarte indica que nosso código nunca usa a variável. Melhoram a sua legibilidade e manutenção.

Você indica que uma variável é um descarte atribuindo-lhe o sublinhado (_) como seu nome. Por exemplo, a chamada de método a seguir retorna uma tupla na qual o primeiro e o segundo valores são descartados. area é uma variável declarada anteriormente definida para o terceiro componente retornado por GetCityInformation:

(_, _, area) = city.GetCityInformation(cityName);

Você pode usar descartar para especificar parâmetros de entrada não utilizados de uma expressão lambda. Para obter mais informações, consulte a seção Parâmetros de entrada de uma expressão lambda do artigo Expressões do Lambda.

Quando _ é um descarte válido, tentar recuperar seu valor ou usá-lo em uma operação de atribuição gera erro de compilador CS0103, "O nome '_' não existe no contexto atual". Este erro ocorre porque _ não é atribuído um valor e pode nem mesmo ser atribuído um local de armazenamento. Se fosse uma variável real, você não poderia descartar mais de um valor, como fez o exemplo anterior.

Tupla e desconstrução de objetos

Os descartes são úteis para trabalhar com tuplas quando o código do aplicativo usa alguns elementos de tupla, mas ignora outros. Por exemplo, o método a seguir QueryCityDataForYears retorna uma tupla com o nome de uma cidade, sua área, um ano, a população da cidade para esse ano, um segundo ano e a população da cidade para esse segundo ano. O exemplo mostra a mudança da população entre esses dois anos. Dos dados disponíveis da tupla, não estamos preocupados com a área da cidade, e sabemos o nome da cidade e as duas datas no momento do projeto. Como resultado, estamos interessados apenas nos dois valores populacionais armazenados na tupla e podemos lidar com seus valores restantes como descartáveis.

var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010);

Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}");

static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2)
{
    int population1 = 0, population2 = 0;
    double area = 0;

    if (name == "New York City")
    {
        area = 468.48;
        if (year1 == 1960)
        {
            population1 = 7781984;
        }
        if (year2 == 2010)
        {
            population2 = 8175133;
        }
        return (name, area, year1, population1, year2, population2);
    }

    return ("", 0, 0, 0, 0, 0);
}
// The example displays the following output:
//      Population change, 1960 to 2010: 393,149

Para obter mais informações sobre como desconstruir tuplas com devoluções, consulte Desconstruindo tuplas e outros tipos.

O Deconstruct método de uma classe, estrutura ou interface também permite recuperar e desconstruir um conjunto específico de dados de um objeto. Você pode usar descartáveis quando estiver interessado em trabalhar com apenas um subconjunto dos valores desconstruídos. O exemplo a seguir desconstrói um Person objeto em quatro cadeias de caracteres (o nome e o sobrenome, a cidade e o estado), mas descarta o sobrenome e o estado.

using System;

namespace Discards
{
    public class Person
    {
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }
        public string City { get; set; }
        public string State { get; set; }

        public Person(string fname, string mname, string lname,
                      string cityName, string stateName)
        {
            FirstName = fname;
            MiddleName = mname;
            LastName = lname;
            City = cityName;
            State = stateName;
        }

        // Return the first and last name.
        public void Deconstruct(out string fname, out string lname)
        {
            fname = FirstName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string mname, out string lname)
        {
            fname = FirstName;
            mname = MiddleName;
            lname = LastName;
        }

        public void Deconstruct(out string fname, out string lname,
                                out string city, out string state)
        {
            fname = FirstName;
            lname = LastName;
            city = City;
            state = State;
        }
    }
    class Example
    {
        public static void Main()
        {
            var p = new Person("John", "Quincy", "Adams", "Boston", "MA");

            // Deconstruct the person object.
            var (fName, _, city, _) = p;
            Console.WriteLine($"Hello {fName} of {city}!");
            // The example displays the following output:
            //      Hello John of Boston!
        }
    }
}

Para obter mais informações sobre como desconstruir tipos definidos pelo usuário com descartes, consulte Desconstruindo tuplas e outros tipos.

Correspondência de padrões com switch

O padrão de descarte pode ser usado na correspondência de padrão com a expressão switch. Todas as expressões, incluindo null, correspondem sempre ao padrão de eliminação.

O exemplo a seguir define um ProvidesFormatInfo método que usa uma switch expressão para determinar se um objeto fornece uma IFormatProvider implementação e testa se o objeto é null. Ele também usa o padrão de descarte para manipular objetos não nulos de qualquer outro tipo.

object?[] objects = [CultureInfo.CurrentCulture,
                   CultureInfo.CurrentCulture.DateTimeFormat,
                   CultureInfo.CurrentCulture.NumberFormat,
                   new ArgumentException(), null];
foreach (var obj in objects)
    ProvidesFormatInfo(obj);

static void ProvidesFormatInfo(object? obj) =>
    Console.WriteLine(obj switch
    {
        IFormatProvider fmt => $"{fmt.GetType()} object",
        null => "A null object reference: Its use could result in a NullReferenceException",
        _ => "Some object type without format information"
    });
// The example displays the following output:
//    System.Globalization.CultureInfo object
//    System.Globalization.DateTimeFormatInfo object
//    System.Globalization.NumberFormatInfo object
//    Some object type without format information
//    A null object reference: Its use could result in a NullReferenceException

Chamadas para métodos com out parâmetros

Ao chamar o Deconstruct método para desconstruir um tipo definido pelo usuário (uma instância de uma classe, estrutura ou interface), você pode descartar os valores de argumentos individuais out . Mas você também pode descartar o valor dos out argumentos ao chamar qualquer método com um out parâmetro.

O exemplo a seguir chama o método DateTime.TryParse(String, out DateTime) para determinar se a representação de cadeia de caracteres de uma data é válida na cultura atual. Como o exemplo se preocupa apenas em validar a cadeia de caracteres de data e não em analisá-la para extrair a data, o out argumento para o método é um descarte.

string[] dateStrings = ["05/01/2018 14:57:32.8", "2018-05-01 14:57:32.8",
                      "2018-05-01T14:57:32.8375298-04:00", "5/01/2018",
                      "5/01/2018 14:57:32.80 -07:00",
                      "1 May 2018 2:57:32.8 PM", "16-05-2018 1:00:32 PM",
                      "Fri, 15 May 2018 20:10:57 GMT"];
foreach (string dateString in dateStrings)
{
    if (DateTime.TryParse(dateString, out _))
        Console.WriteLine($"'{dateString}': valid");
    else
        Console.WriteLine($"'{dateString}': invalid");
}
// The example displays output like the following:
//       '05/01/2018 14:57:32.8': valid
//       '2018-05-01 14:57:32.8': valid
//       '2018-05-01T14:57:32.8375298-04:00': valid
//       '5/01/2018': valid
//       '5/01/2018 14:57:32.80 -07:00': valid
//       '1 May 2018 2:57:32.8 PM': valid
//       '16-05-2018 1:00:32 PM': invalid
//       'Fri, 15 May 2018 20:10:57 GMT': invalid

Uma eliminação autónoma

Você pode usar um descarte autônomo para indicar qualquer variável que você optar por ignorar. Um uso típico é usar uma atribuição para garantir que um argumento não seja nulo. O código a seguir usa um descarte para forçar uma atribuição. O lado direito da atribuição usa o operador coalescing nulo para lançar um System.ArgumentNullException quando o argumento é null. O código não precisa do resultado da atribuição, por isso é descartado. A expressão força uma verificação nula. O descarte esclarece sua intenção: o resultado da tarefa não é necessário ou usado.

public static void Method(string arg)
{
    _ = arg ?? throw new ArgumentNullException(paramName: nameof(arg), message: "arg can't be null");

    // Do work with arg.
}

O exemplo a seguir usa um descarte autônomo para ignorar o objeto retornado por uma operação assíncrona Task . A atribuição da tarefa tem o efeito de suprimir a exceção que a operação lança quando está prestes a ser concluída. Ele deixa sua intenção clara: você deseja descartar o , e ignorar quaisquer erros gerados a partir dessa operação assíncrona Task.

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    _ = Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");
}
// The example displays output like the following:
//       About to launch a task...
//       Completed looping operation...
//       Exiting after 5 second delay

Sem atribuir a tarefa a um descarte, o código a seguir gera um aviso do compilador:

private static async Task ExecuteAsyncMethods()
{
    Console.WriteLine("About to launch a task...");
    // CS4014: Because this call is not awaited, execution of the current method continues before the call is completed.
    // Consider applying the 'await' operator to the result of the call.
    Task.Run(() =>
    {
        var iterations = 0;
        for (int ctr = 0; ctr < int.MaxValue; ctr++)
            iterations++;
        Console.WriteLine("Completed looping operation...");
        throw new InvalidOperationException();
    });
    await Task.Delay(5000);
    Console.WriteLine("Exiting after 5 second delay");

Nota

Se você executar qualquer um dos dois exemplos anteriores usando um depurador, o depurador interromperá o programa quando a exceção for lançada. Sem um depurador anexado, a exceção é silenciosamente ignorada em ambos os casos.

_ é também um identificador válido. Quando usado fora de um contexto suportado, é tratado não como um descarte, _ mas como uma variável válida. Se um identificador nomeado _ já estiver no escopo, o uso de _ como um descarte autônomo pode resultar em:

  • Modificação acidental do valor da variável no âmbito _ atribuindo-lhe o valor da eliminação pretendida. Por exemplo:
    private static void ShowValue(int _)
    {
       byte[] arr = [0, 0, 1, 2];
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Um erro do compilador por violar a segurança do tipo. Por exemplo:
    private static bool RoundTrips(int _)
    {
       string value = _.ToString();
       int newValue = 0;
       _ = Int32.TryParse(value, out newValue);
       return _ == newValue;
    }
    // The example displays the following compiler error:
    //      error CS0029: Cannot implicitly convert type 'bool' to 'int'
    
  • Erro do compilador CS0136, "Um local ou parâmetro chamado '_' não pode ser declarado neste escopo porque esse nome é usado em um escopo local para definir um local ou parâmetro." Por exemplo:
     public void DoSomething(int _)
    {
     var _ = GetValue(); // Error: cannot declare local _ when one is already in scope
    }
    // The example displays the following compiler error:
    // error CS0136:
    //       A local or parameter named '_' cannot be declared in this scope
    //       because that name is used in an enclosing local scope
    //       to define a local or parameter
    

Consulte também