Поделиться через


Дискарды — основы C#

Отбрасываемые или неиспользуемые переменные — это специально игнорируемые переменные в коде приложения. Отмена эквивалентна неназначенных переменных; У них нет значения. Отмена передает намерение компилятору и другим пользователям, которые считывают код: вы намерены игнорировать результат выражения. Может потребоваться игнорировать результат выражения, одного или нескольких элементов выражения кортежа, out параметра метода или целевого объекта выражения сопоставления шаблонов.

Отбрасывание делает интенцию вашего кода ясной. Отклонение указывает, что код никогда не использует переменную. Они повышают удобочитаемость и удобство обслуживания.

Вы указываете, что переменная является отбрасываемой, назначив ей символ подчеркивания (_) в качестве имени. Например, следующий вызов метода возвращает кортеж, в котором первое и второе значения игнорируются. area — это ранее объявленная переменная, возвращаемая третьим компонентом GetCityInformation:

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

Можно использовать отсеивания для указания неиспользуемых входных параметров лямбда-выражения. Дополнительные сведения см. в разделе Входные параметры лямбда-выраженияСтатья о лямбда-выражениях.

Если _ является допустимым для отбрасывания, попытка получить его значение или использовать его в операции присваивания создает ошибку компилятора CS0103, "Имя "_" не существует в текущем контексте". Эта ошибка связана с тем, что _ не назначено значение и даже не может быть назначено место хранения. Если это была фактическая переменная, вы не могли отменить несколько значений, как сделал предыдущий пример.

Деконструкция кортежей и объектов

Подстановочные символы полезны при работе с кортежами, когда код приложения использует некоторые элементы кортежа, но игнорирует другие. Например, следующий QueryCityDataForYears метод возвращает кортеж с именем города, его площадью, годом, населением города в течение этого года, вторым годом и населением города в течение этого второго года. В примере показано изменение численности населения за эти два года. Из доступных в кортеже данных нас не интересует площадь города, а название города и две даты известны нам уже на этапе разработки. Следовательно, нас интересуют только два значения численности населения, которые хранятся в кортеже. Остальные значения можно обработать как пустые переменные.

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

Дополнительные сведения о деконструкции кортежей с отбрасыванием можно найти в разделе "Деконструкция кортежей и других типов".

Метод Deconstruct класса, структуры или интерфейса также позволяет извлекать и деконструировать определенный набор данных из объекта. Вы можете использовать отбросы, если вы хотите работать только с подмножеством разложенных значений. В следующем примере деконструируется объект Person на четыре строки (имя и фамилия, город и штат), но отбрасываются фамилия и штат.

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

Дополнительные сведения о деконструкции определяемых пользователем типов с пропусками см. в разделе «Деконструкция кортежей и других типов».

Сопоставление шаблонов с switch

Шаблон отмены можно использовать в сопоставлении шаблонов с выражением switch. Каждое выражение, в том числе null, всегда соответствует шаблону отбрасывания.

В следующем примере определяется метод ProvidesFormatInfo, использующий выражение switch для определения того, предоставляет ли объект реализацию IFormatProvider и проверяет, является ли объект null. Он также использует схему отбрасывания для обработки объектов любого другого типа, отличных от NULL.

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

Вызовы методов с out параметрами

При вызове Deconstruct метода для деконструкции определяемого пользователем типа (экземпляр класса, структуры или интерфейса) можно отменить значения отдельных out аргументов. Но можно также отменить значение out аргументов при вызове любого метода с параметром out .

В следующем примере вызывается метод DateTime.TryParse(String, out DateTime), чтобы определить, допустимо ли строковое представление даты в текущих настройках культуры. Так как пример связан только с проверкой строки даты, а не с синтаксическим анализом для извлечения даты, аргумент out метода является отбрасыванием.

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

Автономное отбрасывание

Можно использовать автономную отмену, чтобы указать любую переменную, которую вы решили игнорировать. Один из типичных способов — использовать присваивание, чтобы убедиться, что аргумент не является NULL. Следующий код использует отмену для принудительного назначения. В правой части назначения используется оператор объединения NULL для создания аргумента System.ArgumentNullExceptionnull. Код не нуждается в результатах назначения, поэтому он удаляется. Выражение принудительно проверяет значение NULL. Отмена объясняет намерение: результат назначения не нужен или не используется.

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

    // Do work with arg.
}

В следующем примере используется самостоятельное отбрасывание, чтобы игнорировать объект Task, возвращаемый асинхронной операцией. Назначение задачи приводит к подавлению исключения, которое выбрасывается операцией перед завершением. Это делает намерение понятным: вы хотите отменить 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

Не назначая задачу переменной discard, следующий код вызывает предупреждение компилятора.

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");

Замечание

При выполнении одного из предыдущих двух примеров с помощью отладчика отладчик остановит программу при возникновении исключения. Без присоединенного отладчика исключение автоматически игнорируется в обоих случаях.

_ также является допустимым идентификатором. При использовании вне поддерживаемого контекста _ рассматривается не как отмена, а как допустимая переменная. Если идентификатор с именем _ уже находится в области видимости, использование _ в качестве самостоятельного пропуска может привести к:

  • Случайное изменение значения переменной в области _ путем назначения ему значения предполагаемого удаления. Рассмотрим пример.
    private static void ShowValue(int _)
    {
       byte[] arr = [0, 0, 1, 2];
       _ = BitConverter.ToInt32(arr, 0);
       Console.WriteLine(_);
    }
     // The example displays the following output:
     //       33619968
    
  • Ошибка компилятора из-за нарушения безопасности типов. Рассмотрим пример.
    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'
    

См. также