Dis карта s — основы C#

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

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

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

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

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

Если _ это допустимое исключение карта, попытка получить его значение или использовать его в операции назначения создает ошибку компилятора 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. В примере также используется шаблон пустой переменной для обработки объектов любого другого типа с определенным значением.

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.ArgumentNullException, когда аргумент имеет значение null. Коду не требуется результат присваивания, поэтому используется пустая переменная. Выражение вызывает проверку значения 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

Если задача не назначена пустой переменной, следующий код создает предупреждение компилятора:

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'
    
  • Ошибка компилятора CS0136: "Локальный или параметр с именем "_" нельзя объявить в этом область, так как это имя используется в локальном область для определения локального или параметра". Например:
     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
    

См. также